多线程
1多线程的两种使用方式
第一种:继承Thread类(不建议使用,因为无法多继承)
第二种:实现Runnable接口(建议使用)
栗子:直接用第二种
写一个类实现Runnable接口,并覆盖其中的方法
/*
* 1,该类实现Runnable接口
* 2,实现run()方法
* 3,实例化该类,在new个Thread(Runnable),传入该类,用start启动
* */
class MyThreads implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
//线程都是有名字的,可以用Thread.currentThread()返回Thread对象,在掉用getName()获取该线程的名字
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
启动这个线程需要start()方法,因为Runnable接口中没有这个方法,所以需要Thread类来启动 public static void main(String[] args) {
//1先实例化出自己的线程类
MyThreads mt = new MyThreads();
//2要用start()方法启动线程,因为接口没有该方法所以需要Thread
//Threa可以传入Runnable参数,还可以传入一个线程的名字
Thread th = new Thread(mt,"MyThread");
//3要用start()方法启动线程
th.start();
//main自己跑一个线程
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
这样两个线程就会交替的跑起来
2线程的调度
看最顶层的图:1,Statr()让线程进入Runnable状态(等待调用),调度表中轮到该线程时,就会进入Running(正在运行)
2,如果这时我不想在跑了,可以调用yield()返回到Runnable(等待调用状态),(注意的是对多核的计算机可能无效)
3,sleep()让该线程去睡觉,进入Blocke(睡觉状态,不会在去竞争),直到睡觉时间结束。
4,join(),让该线程一直运行完,其他的线程才能去运行
5,synchronized(同步)关键字会让一段代码上锁,直到这段代码运行完(线程的同步)
6,wait()方法让该线程挂起来(暂停),不在参与竞争
7,notify()方法会让线程从新运行起来,注意的是要在线程都要挂在同一个对象上(也就是同一把钥匙),并且是随机唤醒其中的任意对象。
3线程的终止
不应该调用stop()方法,这样不安全(并且已经过时),最佳停止方法是自己写一个停止方法,在该方法内释放资源
看栗子:
/*
* 最佳停止方法
* 1,定义一个flag
* 2,break跳出循环
* 3,写一个停止方法
* */
class MyThre implements Runnable{
//1定义一个flag
private boolean flag = true;
@Override
public void run() {
for(int i=0;i<100;i++){
//2判断停止
if(!flag) break;
System.out.println("iiii"+i);
}
}
//停止方法
public void stopThread(){
flag = false;
//可以在这里释放资源
}
}
条件满足是,掉用停止方法 public static void main(String[] args) {
MyThre mt = new MyThre();
Thread t = new Thread(mt);
t.start();
//main自己的线程
while(true){
if(mt.i>=30){ //当条件满足时,调用停止方法
mt.stopThread();
break;
}
}
}
4线程的同步
线程同步可以解决多线程竞争同一个资源保证其安全的一种手段
线程同步要用synchronized关键字,并且需要一个任意对象做为钥匙
当方法声明为同步方法是,会用this做为钥匙
栗子:
/*
* 线程同步
* */
class MyThreads implements Runnable{
//定义一把钥匙,也可以用this做钥匙
Object o = new Object();
int index = 0;
//实现的方法
@Override
public void run() {
synchronized (o) {
for(index =0;index<1000;index++){
System.out.println(Thread.currentThread().getName()+": "+index);
}
}
}
}
public static void main(String[] args) {
MyThreads mt = new MyThreads();
//实例化两个线程
//这样就不会同时修改一个数据了
Thread t1 = new Thread(mt,"T1");
Thread t2 = new Thread(mt,"T2");
t1.start();
t2.start();
}
5同步问题(死锁)
用了线程同步后,如果操作不当会造成死锁等问题
栗子:
/*
* 线程死锁
* */
class MyThreads implements Runnable{
//定义两把钥匙
Object key1 = new Object();
Object key2 = new Object();
//
boolean flag = true;
//实现的方法
@Override
public void run() {
//1判断是否进入,
if(flag){
synchronized (key1) { //好人拿第一把钥匙进入
//2进来后就为false
flag = false;
System.out.println(Thread.currentThread().getName()+"我拿key1进来了");
//注意了,万一去睡觉了
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//拿第二把钥匙第二次进入
synchronized (key2) {
System.out.println(Thread.currentThread().getName()+"我拿key2进来了");
}
}
}else{
//第二条线程坏人先拿第二把钥匙进入
synchronized (key2) {
//进来后要设为true,能让线程一第二次进入
flag = true;
System.out.println(Thread.currentThread().getName()+"我拿key2进来了");
//注意了,万一去睡觉了
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//该线程要拿第一把钥匙再次进入
synchronized (key1) {
System.out.println(Thread.currentThread().getName()+"我拿key1进来了");
}
}
}
}
}
启动线程 public static void main(String[] args) {
MyThreads mt = new MyThreads();
//实例化两个线程
//这样就不会同时修改一个数据了
Thread t1 = new Thread(mt,"好人");
Thread t2 = new Thread(mt,"坏人");
t1.start();
t2.start();
}
解析:一般都是同步嵌套容易造成死锁问题
第一条线程好人拿着第一把钥匙进去,然后就跑去睡觉(false了),这样第二条线程坏人就会拿着第二把钥匙进去,然后它又睡觉去了(true了),就轮到好人要拿第二把钥匙进去,但是这第二把钥匙在坏人手中,它又在睡觉,好人就永远不可能等到第二把钥匙,这样就会等到死,造成坏人也不不可能拿到第一钥匙。
6生产者和消费者(wait和notify)
假如:一个厨师不停的做食物,做一个顾客就吃一个,直到厨师做完
厨师就是生产者,顾客就是消费者
看例子:注意的是notify需要同一个对象做钥匙
厨师类:
/*
* 生产者:厨师
* */
class Cool implements Runnable{
String[] food; //一组要生产的食物
String foods; //生产好的食物
boolean falg; //判断是否做了食物
//构造方法
public Cool() {
food = new String[]{"米饭","汉堡","沙拉","火锅","排骨"};
falg = false; //开始是没做食物的
}
//做食物方法
public void make(){
//因为要一直做完才能拿去给顾客,所以要同步
//用自己做钥匙,应为顾客中也有厨师这个对象,要使用同一个对象做钥匙
synchronized (this) {
//判断是否做了食物
if(!falg){
try {
int index = new Random().nextInt(food.length);
foods = food[index]; //这里随机生产食物
System.out.println(Thread.currentThread().getName()+":生产了"+foods);
//生产完就要将falg设为true
falg = true;
//生产完本来是要挂自己,然后等顾客吃完再notify自己
//但是因为notify是随机唤醒线程,所以万一顾客先抢到线程,顾客就会先挂起
//所以这里就先要将顾客线程先唤醒释放掉,然后再挂起自己
this.notify();
//然后再挂起自己
this.wait(); //wait()会抛异常,需要捕获
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//覆盖的方法
@Override
public void run() {
//不停的生产食物
for(int i=0;i<10;i++){
make();
}
}
}
顾客类:
/*
* 消费者:顾客
* */
class Customer implements Runnable{
//需要厨师这个对象
Cool c;
//构造方法
public Customer(Cool c) {
this.c = c;
}
//吃方法
public void eat(){
// 一直要吃完才通知厨师去做,所以需要同步
//注意的要用同意把钥匙做对象
//这里要用厨师做钥匙
synchronized (c) {
//判断厨师是否做了食物
if(c.falg){
try {
System.out.println(Thread.currentThread().getName()+" :正在吃 "+c.foods);
//吃完要叫厨师做,先设为false,让食物为空,在唤醒厨师的线程
c.falg = false;
c.notify();
//吃完将自己挂起来,等厨师去做
c.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//这里如果是顾客先抢到线程,但厨师还没做,这时就要把自己挂起来,等厨师的线程去做
//所以为什么前面先要唤醒线程,在去挂起
try {
c.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//实现方法
@Override
public void run() {
//不停的吃
while(true){
eat();
}
}
}
启动类:
public static void main(String[] args) {
Cool c = new Cool(); //实例化厨师
Customer cu = new Customer(c); //实例化顾客
//创建线程,并未线程命名
Thread ct = new Thread(c,"厨师");
Thread cut = new Thread(cu,"顾客");
//这里注意的是因为顾客是while死循环不停吃食物
//当厨师生产完所有实物后要终止程序,所以要将顾客线程设为后台线程
//后台线程的意思是:当所有线程结束时,这个线程也就结束了。
//用setDaemon(true)方法,注意这个方法一定要在线程启动之前声明
cut.setDaemon(true);
//然后再启动线程
ct.start();
cut.start();
}
输出:
厨师:生产了火锅
顾客 :正在吃 火锅
厨师:生产了米饭
顾客 :正在吃 米饭
厨师:生产了沙拉
顾客 :正在吃 沙拉
厨师:生产了米饭
顾客 :正在吃 米饭
..........
........