1多线程(理解)
(1)JDK5以后的针对线程的锁定操作和释放操作
Lock锁
(2)死锁问题的描述和代码体现
package day24_Thread;
public class DieLock extends Thread {
//定义两把锁
private static final Object objA=new Object();
private static final Object objB=new Object();
//标志位
private boolean flag;
//通过传递构造参数设置标志位
public DieLock(boolean flag) {
// TODO Auto-generated constructor stub
this.flag=flag;
}
//重写run方法
@Override
public void run() {
// TODO Auto-generated method stub
if(flag){
synchronized (objA) {
System.out.println("if objA");
synchronized (objB) {
System.out.println("if objB");
}
}
}else{
synchronized (objB) {
System.out.println("else objB");
synchronized (objA) {
System.out.println("else objA");
}
}
}
}
}
package day24_Thread;
/*
* 同步的效率:
* A:效率低
* B:容易产生死锁
*
* 死锁:
* 两个或两个以上的线程在争夺资源的过程中,发生一种相互等待的现象
*
* 举例:
* 中国人,美国人吃饭
* 正常情况:
* 中国人:筷子两只
* 美国人:刀和叉
*
* 现在:
* 中国人:筷子一只,刀一把
* 美国人:筷子一只,叉一把
* */
public class DieLockDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建对象
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
//启动线程
dl1.start();
dl2.start();
}
}
(3)生产者和消费者多线程体现(线程间通信问题)
以学生作为资源来实现
资源类:Student
设置数据类:SetThread()生产者
获取数据类:GetThread()消费者
测试类:StudentDemo
代码:
A:最基本的版本,只有一个数据
B:改进版本,给出了不同的数据,并加入了同步机制
C:等待唤醒机制改进该程序,让数据能够实现依次的出现
wait()
notify()
notifyAll()多生产多消费
D:等待唤醒机制的代码优化,把数据及操作都写在了资源类中。
package day24_Thread_Pro_Customer2;
public class Student {
//设为私有,其他成员不能直接访问
private String name;
private int age;
private boolean flag;//默认是没有数据,如果是true,说明有数据
//设置方法
public synchronized void set(String name,int age){
if(this.flag){
//有数据就等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//没有数据就设置数据
this.name=name;
this.age=age;
//将标志位置为true
this.flag=true;
//唤醒
this.notify();
}
//获取方法
public synchronized void get(){
if(!this.flag){
//没有数据就等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//有数据就取数据
System.out.println(this.name+"---"+this.age);
//标志位为false
this.flag=false;
//唤醒
this.notify();
}
}
package day24_Thread_Pro_Customer2;
public class SetThread implements Runnable {
private Student s;
private int x=0;
public SetThread(Student s) {
// TODO Auto-generated constructor stub
this.s=s;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//同步代码块
synchronized (s) {
//没有则继续产生,执行后面代码
if(x%2==0){
s.set("林青霞", 27);
}else{
s.set("刘毅", 30);
}
x++;
}
}
}
}
package day24_Thread_Pro_Customer2;
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
// TODO Auto-generated constructor stub
this.s=s;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized (s) {
s.get();
}
}
}
}
package day24_Thread_Pro_Customer2;
/*
* 分析:
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*
* 问题1:按照思路写代码,发现数据每次都是:null -- 0
* 原因:我们在每个数据中创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个。
*
* 如何实现?
* 在外界把这个数据创建出来,通过构造方法传递给其他的类。(Student s)
*
* 问题2:为了数据的效果更好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
* A:同一个数据出现多次
* B:姓名和年龄不匹配
* 原因:
* A:同一个数据出现多次
* CPU的一点点时间片的执行权,就足够你执行很多次
* B:姓名和年龄不匹配
* 线程运行的随机性
* 线程安全问题:
* A:是否是多线程环境 是
* B:是否有共享数据 是
* C:是否有多条语句操作共享数据 是
* 解决方案:
* 加锁
* 注意:
* A:不同种类的线程都要加锁
* B:不同的线程加的锁必须是同一把锁
*
* 问题3:虽然数据安全了,但是呢,一次一大片不好看,我想依次的一次一个输出
* 如何实现呢?
* 通过Java提供的等待唤醒机制解决
*
* 等待唤醒:
* Object类提供了三个方法:
* wait():等待
* notify():唤醒单个线程
* notifyAll():唤醒所有线程
* 为什么这些方法不定义在Thread类中呢?
* 这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是在任意锁对象。
* 所以,这些方法必须定义在Object类中。
*
* 最终版代码:
* 把Student的成员变量给私有的了。
* 把设置和获取的操作给封装成了功能,并加了同步。
* 设置或者获取的线程里面值需要调用方法即可。
* */
public class StudentDemo {
public static void main(String[] args) {
//创建资源
Student s = new Student();
//设置获取类和获取类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t1.start();
t2.start();
}
}
(4)线程组
package day24_ThreadGroup;
public class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package day24_ThreadGroup;
/*
* 线程组:把多个线程组合到一起
* 它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
* */
public class ThreadGroupDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
method1();
System.out.println(Thread.currentThread().getThreadGroup().getName());
//我们何如修改线程所在的组呢?
//创建一个线程组
//创建其他线程的时候,把其他线程的组指定为我们自己新建的线程组
method2();
}
private static void method2() {
// TODO Auto-generated method stub
ThreadGroup tg = new ThreadGroup("这是一个新的线程组");
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(tg,my,"林青霞");
Thread t2 = new Thread(tg,my, "刘意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//利用线程组统一修改该组数据
//通过组名称设置后台线程,表示该组的线程都是后台线程。
t1.setDaemon(true);
}
public static void method1(){
MyRunnable my = new MyRunnable();
//创建线程
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "刘意");
//我不知道他们属于哪个线程组,怎么办?
//线程组里方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
//线程组的方法:public final String getName()
String name1=tg1.getName();
String name2=tg2.getName();
System.out.println(name1);
System.out.println(name2);
//通过结果我们知道了,线程默认情况下属于main线程组
}
}
(5)线程池
package day24_ThreadPool;
public class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package day24_ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 线程池的好处:线程池的每一个线程代码结束后,都不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用
*
* 如何实现线程的代码呢?
* A:创建一个线程池的对象,控制要创建几个线程对象
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:这种线程池的线程可以执行
* 可以执行Runnable对象或者Callable对象代表的线程
* 做一个类实现Runnable接口
* C:调用如下方法即可
* Future<?>submit (Runnable task)
* <T> Future<T> submit(Callable<T> task)
*
* */
public class ExecutorsDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一个线程池对象,控制要创建几个线程对象
//public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);
//可以执行Runable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
(6)多线程实现的第三种方案
package day24_Callable;
import java.util.concurrent.Callable;
/*
* 多线程求和
* */
public class MyCallable implements Callable<Integer> {
private int number;
//通过构造方法传递number
public MyCallable(int number) {
// TODO Auto-generated constructor stub
this.number=number;
}
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
int sum=0;
for(int i=1;i<=number;i++){
sum+=i;
}
return sum;
}
}
package day24_Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/*
* 线程池的好处:线程池的每一个线程代码结束后,都不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用
*
* 如何实现线程的代码呢?
* A:创建一个线程池的对象,控制要创建几个线程对象
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:这种线程池的线程可以执行
* 可以执行Runnable对象或者Callable对象代表的线程
* 做一个类实现Runnable接口
* C:调用如下方法即可
* Future<?>submit (Runnable task)
* <T> Future<T> submit(Callable<T> task)
* */
public class MyCallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// TODO Auto-generated method stub
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
Integer i1=f1.get();
Integer i2 = f2.get();
System.out.println("i1:"+i1);
System.out.println("i2:"+i2);
//结束
pool.shutdown();
}
}
(7)多线程的面试题
1:多线程有几种实现方案,分别是哪几种?
一般答两种:
继承Thread类
实现Runnable接口
拓展一种:实现Callable接口,这个得和线程池结合
2:同步有几种方式,分别是什么?
两种
同步代码块
同步方法
3:启动一个线程是run()还是start()?它们的区别是?
start()
run():封装了被线程执行的代码,直接调用仅仅是普通的调用
statr():启动线程,并有jvm自动调用run()方法
4:sleep()和wait()方法的区别是?
sleep():必须指时间;不释放锁
wait():它可以不指定时间,也可以指定时间;释放锁
5:为什么wait()、notify()、notifyAll()等方法都是定义在Object类中?
因为这些方法调用时依赖于锁对象的,而同步代码块的锁对象是任意锁
而object代表任意的对象,所以,定义在这里面。
6:线程的生命周期图?
新建--就绪--运行--死亡
新建--就绪--运行--阻塞--就绪--运行--死亡
建议:画图解释