Java--多线程
Java--多线程
进程
线程
多线程的实现方式
方法1:继承Thread类
- 定义一个类继承Thread方法
- 再该类中重写run()方法
- 创建该类的对象
- 启动线程
代码示例
类
public class demo extends Thread{//继承Thread方法
@Override
public void run() {//重写run方法,把代码片断放入其中
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
Main方法
public class Main {
public static void main(String[] args) {
demo one = new demo();//
demo two = new demo();//创建两个该类的对对象
// one.run();
// two.run();//如果直接调用run方法就和普通方法的调用一样,没有多线程
one.start();//必须使用start方法调用才能启动多线程
two.start();
}
}
设置和获取线程名称
- 使用setName方法来修改线程名称
- 使用getName方法来返回该线程名称
- 还可以通过构造方法来设置线程名称(注意需要重写子类中的构造方法)
示例代码
Demo
public class Demo extends Thread{//继承Thread方法
public Demo(){
}
public Demo(String name)//重写子类构造方法
{
super(name);//调用父类构造函数
}
@Override
public void run() {//重写run方法,把代码片断放入其中
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);//获取线程名称
}
}
}
Main方法
public class Main {
public static void main(String[] args) {
Demo one = new Demo("线1");//
Demo two = new Demo("线2");//创建两个该类的对对象
//不设置线程名称默认为Thread-0,1,2
//线程名称只能设置一次?
one.start();//必须使用start方法调用才能启动多线程
two.start();
// one.setName("线程1");//使用set方法设置线程名称
// two.setName("线程2");//设置线程名称
// one.start();//必须使用start方法调用才能启动多线程
// two.start();
}
}
线程调度
- 使用getPriority()返回线程的优先级
- 使用setPrioriity()更改线程优先级
线程高只是指获取CPU时间篇的几率高,CPU不是全部被线程优先级高的线程占用
代码示例
Demo类
public class Demo extends Thread{//继承Thread方法
public Demo(){
}
public Demo(String name)//重写子类构造方法
{
super(name);//调用父类构造函数
}
@Override
public void run() {//重写run方法,把代码片断放入其中
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);//获取线程名称
}
}
}
Main方法
public class Main {
public static void main(String[] args) {
Demo one = new Demo();//
Demo two = new Demo();
Demo three = new Demo();
//不设置线程名称默认为Thread-0,1,2
//线程名称只能设置一次?
// one.start();//必须使用start方法调用才能启动多线程
// two.start();
one.setName("飞机");//使用set方法设置线程名称
two.setName("高铁");//设置线程名称
three.setName("汽车");
System.out.println(one.getPriority());
System.out.println(two.getPriority());
System.out.println(three.getPriority());//获取线程优先级
System.out.println(Thread.MAX_PRIORITY);
System.out.println(Thread.MIN_PRIORITY);
one.setPriority(10);
two.setPriority(1);
three.setPriority(1);
one.start();//必须使用start方法调用才能启动多线程
two.start();
three.start();
}
}
实现Runable接口的方式进行实现
- 自己定义一个类实现Runnable接口
- 重写run方法
- 创建自己的类的对象
- 创建一个Thead类的对象,并开启线程
代码示例
实现run方法
public class Myrun implements Runnable {
@Override
public void run() {//实现run方法
for (int i = 1; i <= 100; i++) {
//Thread.currentThread()获取当前线程
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
Main方法
public class Main {
public static void main(String[] args) {
Myrun one = new Myrun();
Myrun two = new Myrun();//创建实现接口的类的对象
Thread t1 = new Thread(one);//创建Thread对象
t1.setName("线程1");
Thread t2 = new Thread(two);//创建Thread对象
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
利用Callable接口和Future接口方式实现
该方法可以获取多线程的结果
Future用来管理多线程的运行结果
Callable用来表示多线程要执行的任务
代码实现
Callable实现类
package Demo01;
import java.util.concurrent.Callable;
public class MyCall implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}//创建类是实现Callable接口
// @Override
// public Integer call() {
//
//
// }
}
Main方法
package Demo01;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCall one =new MyCall();//创建Callable实现类的对象
FutureTask<Integer> t1 = new FutureTask<>(one);//放入创建Callable实现类的对象
Thread Th=new Thread(t1);//创建Thread的对象将 FutureTask填入其中
Th.start();//开启多线程
System.out.println(t1.get());
}
}
多线程三种实现方式的比较
不可获取结果:
继承Thread类
实现Runable接口
可获取结果:
实现Callable类
多线程中的常用成员方法
成员方法的使用细节
代码示例
继承Thread类
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
Main方法
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread("飞机");
MyThread t2 = new MyThread("坦克");//通过构造方法给线程赋值
t1.start();
t2.start();
Thread one = Thread.currentThread();//获取当前线程
System.out.println("当前线程名称:" + one.getName());
// System.out.println(111111);
// Thread.sleep(2000);//休眠2秒
// System.out.println(222222);
}
}
线程优先级
Java采用的是抢占式调度(随机性)
关于优先级的方法
继承Thread类
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
Main方法
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread("飞机");
MyThread t2 = new MyThread("坦克");//通过构造方法给线程赋值
//打印默认优先级
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println("============");
//优先级只是概率问题
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
守护线程
当其他非守护线程执行完毕之后,守护线程会陆续结束.(不是立即结束,是陆续结束)
代码示例
MyThread1
public class MyThread1 extends Thread{
public MyThread1() {
}
public MyThread1(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
System.out.println(getName()+":"+i);
}
}
}
MyThread2
public class MyThread2 extends Thread{
public MyThread2() {
}
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println(getName()+":"+i);
}
}
}
Main方法
public class Main {
public static void main(String[] args) {
MyThread1 one = new MyThread1("女神");
MyThread1 two = new MyThread1("备胎");
two.setDaemon(true);//将第二个线程设置为守护线程
one.start();//女神线程结束之后备胎线程就没有存在的必要了
two.start();
//运行的时候可以看到备胎线程陆续结束
}
}
礼让线程
不太常用
表示出让当前CPU的执行权
可以让结果均匀一点
重写的Thread方法
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + ":" + i);
Thread.yield();//表示出让当前CPU的执行权
}
}
}
Main方法
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread("飞机");
MyThread t2 = new MyThread("坦克");//通过构造方法给线程赋值
t1.start();
t2.start();
}
}
差入线程
不常用
表示把下面的t线程插入到当前线程之前
当前线程:main线程
代码示例
继承的Thread类
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
Main方法
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread("飞机");
t.start();//开启多线程
t.join();//将t线程插入到main线程之前
for (int i = 1; i <=10 ; i++) {
System.out.println("main线程:"+i);
}
//如何在main线程之前插入t线程?,使用join方法
}
}
线程的生命周期
不会立即执行下面的代码,可能还没有抢到执行权
线程的安全问题
例题
代码示例
MyThread
package Demo01;
public class MyThread extends Thread{
static int ticket=1;
@Override
public void run() {
while (true)
{
if (ticket<100)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票.");
}
else
{
break;//票卖完了
}
}
}
}
Main方法
package Demo01;
public class Main {
public static void main(String[] args) {
MyThread one= new MyThread();
one.setName("窗口1");
MyThread two = new MyThread();
two.setName("窗口2");
MyThread three =new MyThread();
three.setName("窗口3");
one.start();
two.start();
three.start();
}
}
但是上面的代码有问题,会出现同时卖一个票和买票超过限额的情况
怎么进行优化?
同步代码块
线程执行具有随机性
卖同一个票的问题
卖超出范围票的问题
需要把共享数据的代码锁起来
-
锁默认打开,有一个线程进去了,锁自动关闭
-
当里面的代码执行完毕,线程出来,锁自动打开
这种方法就叫做同步代码块
只有上一个线程出来,下一个线程才能进去
注意:锁对象,一定要是唯一的
代码示例
MyThread
package Demo01;
public class MyThread extends Thread {
static int ticket = 0;
static Object obj = new Object();//保证锁对象是唯一的
@Override
public void run() {
while (true) {
synchronized (obj)//填入唯一的锁对象
{
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票.");
} else {
break;//票卖完了
}
}
}
}
}
Main
package Demo01;
public class Main {
public static void main(String[] args) {
MyThread one= new MyThread();
one.setName("窗口1");
MyThread two = new MyThread();
two.setName("窗口2");
MyThread three =new MyThread();
three.setName("窗口3");
one.start();
two.start();
three.start();
}
}
同步代码块细节
- 要将synchronized写在循环里面,否则线程1就会一直执行直到线程结束
- 锁对象一定要是唯一的
就是说线程1,2,3要看同一把锁才有意义.否则还会出现上面的问题
经常使用字节码文件对象作为锁对象
package Demo01;
public class MyThread extends Thread {
static int ticket = 0;
static Object obj = new Object();//保证锁对象是唯一的
@Override
public void run() {
while (true) {
synchronized (MyThread.class)//填入唯一的锁对象
{
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票.");
} else {
break;//票卖完了
}
}
}
}
}
同步方法
就是把synchronized关键字加到方法上
技巧:先写同步代码块,然后改为同步方法就可以了
还是上面的那个卖票的例子
代码示例
Myrunable类
public class Myrunable implements Runnable {
int ticket = 0;
@Override
public void run() {
while (true) {
if (extracted()) break;
}
}
private synchronized boolean extracted() {//同步方法
if (ticket >= 100) {
return true;
} else {
ticket++;
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
}
return false;
}
}
Main方法
public class Main {
public static void main(String[] args) {
Myrunable mr= new Myrunable();
Thread one = new Thread(mr);
Thread two = new Thread(mr);
Thread three = new Thread(mr);
one.setName("窗口1");
two.setName("窗口2");
three.setName("窗口3");
one.start();
two.start();
three.start();
}
}
lock锁
这个lock锁就相当于我们可以用两个方法lock()和unlock来手动上锁和开锁,更加灵活.
代码示例
MyThread类
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread{
static int ticket=0;
static ReentrantLock one= new ReentrantLock();
@Override
public void run() {
while (true)
{
one.lock();//在这里锁住
if (ticket==100)
{
break;
}
else
{
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!");
}
one.unlock();//在这里开锁
}
}
}
Main方法
public class Main {
public static void main(String[] args) {
MyThread one = new MyThread();
MyThread two =new MyThread();
MyThread three = new MyThread();
one.setName("窗口1");
two.setName("窗口2");
three.setName("窗口3");
one.start();
two.start();
three.start();
}
}
但是上面的代码有个问题就是卖完100张票之后,程序不会停止运行,所以我们要在每次循环的最后进行一个关锁,可以借助try处理异常的方式来实现.
MyThread
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
static int ticket = 0;
static ReentrantLock one = new ReentrantLock();
@Override
public void run() {
while (true) {
one.lock();//在这里锁住
try {
if (ticket == 100) {
break;
} else {
sleep(100);
ticket++;
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!!");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {//每次结束finally里面肯定会被执行
one.unlock();//在这里开锁
}
}
}
}
死锁
死锁是一个错误
死锁不需要代码训练
注意
写代码的时候不要让两个锁嵌套起来
生产者和消费者(等待唤醒机制)
该模式是一个十分经典的多线程协作的模式
消费者和生成者轮流执行
消费者等待
如果吃货发现没有面条就只能等待,而厨师发现没有面条就会开始做面条,当面条做完了之后,厨师就会唤醒吃货.
生产者等待
常见方法
wait()让当前线程等待,直到其他线程被唤醒
notify()随机唤醒单个线程
notifyAll唤醒说有线程
等待唤醒机制(消费者)
吃货类
按照下面的顺序来写多线程
public class Foodie extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.flag == 0)//如果没有面条
{
try {
Desk.lock.wait();//就等待
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else//如果有面条
{
Desk.count--;//将面条减1
System.out.println("吃货正在吃面条,还能再吃" + Desk.count + "碗");
Desk.lock.notifyAll();//唤醒所有线程
Desk.flag = 0;//修改桌子状态
}
}
}
}
}
Disk类
作用:控制生产者和消费者的执行.
public class Desk {
public static int flag = 0;//0代表桌子上面没有面条,1代表桌子上面有面条
public static int count = 10;//可以吃的面条的碗数
public static Object lock = new Object();
}
等待唤醒机制(生产者)
Cook类
public class Cook extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0)//已经吃了10碗
{
break;
} else {
if (Desk.flag == 1)//当前有面条,cook应该等待
{
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {//如果没有面条
System.out.println("厨师正在制作一碗面条!!!!");
//修改桌子上的状态
Desk.flag = 1;
//叫醒吃货开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
Main方法
public class Main {
public static void main(String[] args) {
Cook one =new Cook();
one.setName("厨师");
Foodie two = new Foodie();
two.setName("吃货");
one.start();
two.start();
}
}
等待唤醒机制(阻塞队列实现)
阻塞队列的继承结构
注意生产者和消费者必须使用同一个阻塞队列.
注意阻塞队列方法内部已经有锁了,不需要再实现锁.
代码示例
Cook类
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread {
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
queue.put("面条");
System.out.println("放入面条");//因为打印语句在锁的外面,所以打印 会有重复的情况出现,但是数据是没有问题的
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
Foodie类
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread {
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
String s = queue.take();
System.out.println("正在吃" + s);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
Main类
import java.util.concurrent.ArrayBlockingQueue;
public class Main {
public static void main(String[] args) {
ArrayBlockingQueue <String> q=new ArrayBlockingQueue<>(1);//创建长度为1的阻塞队列
Cook one =new Cook(q);
Foodie two =new Foodie(q);
one.start();
two.start();
}
}