DAY21-多线程

今日课程:多线程


程序:由逻辑代码和数据组成的集合,存储在磁盘上,是静态的。

进程:正在运行的程序,需要在内存中开辟空间,需要CPU的调度。对于CPU来说,某一时刻他只能执行一个进程,看到的同时的执行的效果,是因为CPU一直在做着高速的切换操作。

线程:可以独立运行的代码片段,属于进程。多个可独立运行的代码片段称之为多线程。一个进程中至少要有一个线程---单线程的进程。

对于之前所写的java程序,都是单线程程序,可独立运行的代码都被封装到main方法中,要创建新的线程,代码都要封装到一个run方法中。

并行:多个任务共同运行,需要多个CPU支持。

并发:多个任务共同运行,只有一个CPU调度。

多线程这种做法提高了CPU的使用率,但是对于某个进程来说可能时间稍长。

多线程执行时,某一线程出现异常,他会停止运行,对其他线程没有影响。

创建线程的两种方式:继承和实现

1.继承Thread类

  1. 自定义一个类extends Thread
  2. 重写run()方法
  3. 创建子类对象
  4. 调用Thread类中的start()方法,启动线程

2.实现接口方式

  1. 定义一个实现类实现Runnable接口
  2. 实现接口中的run()方法
  3. 创建实现类对象
  4. 创建Thread类对象,把实现类对象当做参数传入
  5. 调用Thread类中的start方法

两种方式的异同:

继承Thread类方式

public class Demo1 {
	public static void main(String[] args) {
		// myThread my=new myThread();
		// my.start();//启动线程
		
		// 匿名内部类形式创建多线程
		new Thread() {	//父类名,子类方法
			@Override
			public void run() {
				for (long i = 0; i < 100L; i++) {
					System.out.println("i=" + i);
				}
			}
		}.start();
		for (long i = 0; i < 100L; i++) {
			System.out.println("main=" + i);
		}
	}
}

class myThread extends Thread { // 继承方式实现多线程
	public void run() {
		for (long i = 0; i < 100L; i++) {
			System.out.println("i=" + i);
		}
	}
}

实现类方式:

public class Demo2 {
	public static void main(String[] args) {
//		myThread my=new myThread();//创建自定义类的对象
//		Thread th=new Thread(my);//把自定义类的对象当做参数传给Thread类的构造参数
//		th.start();//启动线程
		
		//匿名内部类形式
/*一层一层分析,首先最外层的是Thread对象,先new他,然后传入的是myThread对象作为参数,我们使用匿名方法,所以用他的
父接口作为new的对象,所以这里要new Runnable(),然后我们再用大括号括起来,大括号里面在写他的实现类myThread改写的
run()方法,最后再.start()调用方法即可*/
		new Thread(new Runnable(){
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("多线程--:" + i);
				}
			}
		}).start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println("主线程:"+i);
		}
	}
}

class myThread implements Runnable {
	// 实现接口形式多线程
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("多线程--:" + i);
		}
	}
}

从代码上看:

  1. 继承方式代码简单
  2. 实现方式代码稍麻烦

从设计角度看:

  1. 继承方式,通过查看start方法的源码,看到了start0方法,该方法是本地方法,不是java语言实现的,可以理解为是JVM启动线程并回来调用我们写的run方法。
  2. 实现方法,通过查看源码发现Thread类中有一个Runnable类型的引用变量target是通过我们编写的Runnable的实现类对象进行复制的,调用start方法时找到了start0,JVM回来调用run方法,发现源码中run方法源码中有一个target.run(),即是父接口类型引用指向实现类对象,即最终调用的是我们编写的run方法。

扩展性

  1. 继承方式:由于java是单继承,因此不能再继承其他类,也有可能某个类需要以线程方式运行,但是与Thread不具备子父类关系,违背继承原则,因此不建议使用该方法
  2. 实现方式,还可以去继承其他类,线程对象和任务分离开,耦合度降低,建议使用该方法。

匿名内部类方式创建线程

代码参考上面两个。

Thread类中的常用构造方法:

Thread() 
          分配新的 Thread 对象。
Thread(Runnable target) //传入一个实现了Runnable接口的对象实例
          分配新的 Thread 对象。
Thread(Runnable target, String name) //传入一个实现了Runnable接口的对象实例和新线程的名字
          分配新的 Thread 对象。
Thread(String name) //创建时传入新线程的名字
          分配新的 Thread 对象

Thread类中的常用方法:

static Thread currentThread() //静态方法,直接Thread.currentThread()调用
          返回对当前正在执行的线程对象的引用。当前谁调用了这个线程就显示谁的名字。
 String getName() 
          返回该线程的名称。
 void

setName(String name) 设置线程名字。

该方法用在线程启动前启动后都可以,即使不设置线程的名字,创建的线程也有默认名称Thread-编号(从0开始)

主线程名字是:main

线程优先级:优先级有1-10级,优先级高的先执行,低的后执行,创建的线程默认优先级为5,主线程main优先级也是5。

 void setPriority(int newPriority) 
          设置线程的优先级。
 int getPriority() 
          返回线程的优先级。

线程休眠:是当前线程休眠设置的毫秒值,时间到了则线程恢复CPU调度。

static void sleep(long millis) //静态方法,直接Thread.sleep()调用
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

守护线程(后台线程):

创建的线程默认为非守护线程,任何线程都可以设置成守护线程和非守护线程。守护线程可以理解为:为了一个线程(前台线程)的存在而存在,如果不存在了守护线程也会退出。

 void setDaemon(boolean on) 
          将该线程标记为守护线程或用户线程。
 boolean isDaemon() 
          测试该线程是否为守护线程。
public class Demo3 {
	public static void main(String[] args)  {
		Ticket ticket = new Ticket();//创建实现类对象
//		Thread t1 = new Thread(ticket, "线程1:");//实现类对象作为参数传入,同时为线程指定名字,
//		Thread t2 = new Thread(ticket, "线程二:");
		
		Thread t1 = new Thread(ticket);//创造对象时没有传入名字的话调用名字是默认的名字
		Thread t2 = new Thread(ticket);
		System.out.println("当前线程的名字为:"+t1.getName());
		System.out.println("当前线程的名字为:"+t2.getName());
		System.out.println("----------------------------------");
		//给线程重新命名
		t1.setName("new线程1- ");//给线程命名放在启动前后效果一样
		t2.setName("new线程二--");
		
		//获取优先级
		System.out.println("t1的优先级为:---"+t1.getPriority());
		System.out.println("t2的优先级为:---"+t2.getPriority());
		System.out.println("----------------------------------");
		
		//设置优先级,注意设置了休眠后会严重影响优先级的顺序
		t1.setPriority(10);	//t1出现的几率远大于t2,知道100个数都遍历完了,t2可能也出现不了几次
		t2.setPriority(1);
		
		//守护线程
		t1.setDaemon(true);
		System.out.println("t1是否是守护线程?---"+t1.isDaemon());
		System.out.println("t2是否是守护线程?---"+t2.isDaemon());
		System.out.println("----------------------------------");
		
		//启动线程
		t1.start();
		t2.start();
	}
}

class Ticket implements Runnable {
	int ticket = 100;

	public void run(){
		while (ticket > 0) {
			try {
				Thread.sleep(100);//休眠,静态方法,直接调用
			} catch (InterruptedException e) {
			}
			System.out.println(Thread.currentThread().getName() + ticket);
			ticket--;
		}
	}
}

多线程出现的安全问题

问题:多线程执行时,操作的是相同的代码,这片相同的代码操作的还是对线程共享的数据,某一线程没有执行完这片代码,另一个线程就参与执行,导致了错误数据产生。--安全问题

解决方式:使用用同步代码块。

synchronized(锁对象){
    多线程共同执行的共享数据代码;
}

好处:解决了多线程的安全问题。

弊端:执行效率降低了。

  1. 前提:至少是2个以上线程
  2. 保证是同一个锁对象,锁对象可以是任意对象,也可以是this

synchronized修饰符,同步的意思,还可以修饰方法:

当用synchronized修饰方法时,这个方法就具备了同步的属性,不会出现多线程的安全问题。

synchronized 返回值类型 方法名(){    //锁对象为this
    语句;
}

synchronized 返回值类型 方法名(){    //锁对象为  类名.class
    语句;
}
public class Demo4 {
	public static void main(String[] args) {
		TTicket tick = new TTicket();

		Thread t1 = new Thread(tick, "线程一号");
		Thread t2 = new Thread(tick, "线程2号--");
		Thread t3 = new Thread(tick, "线程三号--");
		Thread t4 = new Thread(tick, "线程4号--");
		Thread t5 = new Thread(tick, "线程五号--");

		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}

class TTicket implements Runnable {
	private int ticket = 100;
	// Object obj = new Object();// 锁对象,任意对象都可以

	public void run() {
/*注意:这里判断条件不能写ticket>0,因为这样每一个线程都能抢到一个值,只是执行语句被线程锁锁住了没法输出,但是当某一个
线程执行到ticket=1时返回的下一个值是0,这时该线程退出,但是其他线程已经进入了循环,而且此时线程锁也是打开的,所以他们会
继续输出,返回的值为负数,不满足while条件,退出,但是还有线程已经在循环内等待执行,所以他会拿返回的负数值继续操作,
直到所有线程都结束才程序才结束,所以最后会输出0或者是负值,因此判断条件一定要放在同步锁内部,用if来判断。*/
		while (true) {
			
/*加一个同步锁,保证线程安全,同步锁必须放在while内部,因为如果放在外部,第一个线程抢到资源以后同步锁就锁住,然后内部
直接开始循环,循环没结束锁是不会打开的,直到循环结束以后同步锁打开,线程二抢到锁,然后进入循环,但是此时ticket已经为0,
循环直接结束,所以线程二始终都没有输出东西*/
//			synchronized (this) {	//1= 开 ,0 =关
//				if (ticket > 0) {
//					// 打印输出this都是同一个值,验证了同步锁必须是同一对象
//					System.out.println(Thread.currentThread().getName() +"拿到的票为:"+ ticket + "..." + this);
//					try {
//						Thread.sleep(100);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					ticket--;
//				}else
//					break;
//			}
			
			lock();	//还可以单独写一个方法来实现同步锁
			
			if(ticket<1)	//结束无线循环,不能采用lock方法中加return的方法,那样只能结束lock方法的执行而已
				break;
		}
	}
	public synchronized void lock(){	//
		if(ticket > 0){ //判断条件一定要写在循环内部
			System.out.println(Thread.currentThread().getName()+"---"+ticket +"...."+this);
			try {
				Thread.sleep(100); //t2
			} catch (InterruptedException e) {
			}
			ticket--;
		}
//		else{			//注意:return是结束方法的执行的,只能结束本方法,由于调用lock方法的run方法还有一个
//			return;		//所以run方法的无线循环是结束不掉的,必须再加判断
//		}
	}
}

死锁

多线程想要对方的锁,但对方都不释放锁,导致程序运行卡住不在继续运行的现象。实际开发中不能出现死锁的现象,尽量避免同步锁的嵌套使用。

eg:同步的嵌套

//死锁
public class Demo8{
	public static void main(String[] args) {
		deadLock d1=new deadLock(true);//先获取a锁再获取b锁
		deadLock d2=new deadLock(false);//先获取b锁再获取a锁
		Thread t1=new Thread(d1);
		Thread t2=new Thread(d2);
		t1.start();
		t2.start();
	}
}

class deadLock implements Runnable {
	boolean flag;
	public deadLock(boolean flag){
		this.flag=flag;
	}
	public void run() {
		if (flag) {
			synchronized ("a") {
				System.out.println("if----------a");
				synchronized ("b") {
					System.out.println("if------------b");
				}
			}
		}else
			synchronized ("b") {
				System.out.println("else==========b");
				synchronized ("a") {
					System.out.println("else===========a");
				}
			}
	}
}
//匿名内部类方式
public class Demo7 {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			public void run() {
				synchronized ("刀叉") {
					System.out.println(Thread.currentThread().getName() + ":你不给我筷子,我就不给你刀叉");
					synchronized ("筷子") {
						System.out.println(Thread.currentThread().getName() + ": 给你刀叉");
					}
				}
			}
		}, "中国人").start();
		new Thread(new Runnable() {
			public void run() {
				synchronized ("筷子") {
					System.out.println(Thread.currentThread().getName() + ": 先给我刀叉");
					synchronized ("刀叉") {
						System.out.println(Thread.currentThread().getName() + ": 筷子给你");
					}
				}
			}
		}, "美国人").start();
	}
}

猜你喜欢

转载自blog.csdn.net/qq_42837554/article/details/88602593
今日推荐