声明:学基础,在校学生,本文所有内容来自书本和视频,然后通过自己的理解和筛选编写而来,如有理解不到位的写得不到位的地方,欢迎评论指错!!!(仅做学习交流)
笔者:Fhvk
微信:WindowsC-
什么是多线程
- 线程是程序的执行路径,一个进程中可以包括多条线程
- 什么是进程,如下图:
- 多线程并发执行可以提高程序的执行效率,可以同时完成多项目工作,例如使用电脑管家同时进行电脑清理和电脑加速,这就属于多线程;
- 多线程的应用场景
1、比如qq和多个人多天
2、比如果百度网盘同时下载多个文件
3、服务器处理多个请求等
多线程的原理:多线程说是说同时执行,但其实底层CPU执行还是一条一条执行,只是CPU执行速度非常之快。达到了多线程的效果,但现在电脑都已经达到双核和多核同时执行了。
多线程的并行和并发
- 并行:就是两个任务同时执行,就是任务1 在执行,任务2也在执行,前提是多核CPU
- 并发:是指两个任务同时请求执行,而CPU只能执行接收一个任务,就把这两个任务轮流执行,由于执行速度快,时间间隔短,使用户感觉同时执行;
Java程序运行原理和JVM的启动是多线程的吗
- Java程序运行原理:Java命令会启动JVM,等同于启动一个程序,也就是启动了一个进程,该进程会自动启动一个"主线程",然后主线程去调用某个类的main方法;
- JVM启动是多线程的吗?很明显是,finalize()方法是在垃圾回收的方法,由JVM自动调用。
/**
* 证明JVM是多线程的
* @author fhvk.game
* */
public class Demo1_Thread {
public static void main(String[] agrs) {
for(int i = 0; i < 1000000; i++) {
new Demo();
/*当垃圾达到了一定的数量,
会启动垃圾回收线程会自动调用finalize方法进行垃圾回收*/
}
//然后main本来就是一条线程,主线程
for(int j = 0; j < 10000; j++) {
System.out.println("你好我是主线程");
}
}
}
class Demo {
//重写finalize只输出
public void finalize() {
System.out.println("垃圾被清理");
}
}
输出结果:
你好我是主线程
你好我是主线程
你好我是主线程
垃圾被清理
垃圾被清理
你好我是主线程
你好我是主线程
垃圾被清理
垃圾被清理
多线程2种实现方式
- 继承Thread
1、定义类继承Thread;
2、重写run()方法
3、将要执行的语句写在run()方法中
4、创建Thread子类实例对象
5、通过start()方法开启线程
public class Demo1_Thread {
public static void main(String[] agrs) {
Demo d = new Demo();
d.start();
}
}
class Demo extends Thread{
public void run() {
for(int i = 0; i < 1000; i++) {
Syetm.out.println("我是Thread"):
}
}
}
- 实现Runnable接口
1、定义类实现Runnable接口
2、重写run()方法
3、执行语句放run()方法中
4、创建Runnable实现类对象
5、创建Thread对象
6、将Runnable实现类对象传到Thread构造中
7、通过Thread对象start()方法启动线程
public class Demo_Runnable {
public static void main(String[] agrs) {
RunnableDemo rd = new RunnableDemo();
Thread t = new Thread(rd);
t.start();
}
}
class RunnableDemo implements Runnable {
public void run() {
System.out.println("我是Runnable");
}
}
- 实现Runnable的原理
看Thread的构造方法,传递了Runnable接口的引用,通过其中init()方法找到传递的target给成员变量target赋值,查看Thread中run()方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run()方法
- 两种多线程实现方式的区别
查看源代码的区别
1、Thread:子类继承Thread,由于重写了run()方法当调用start()方法时,直接找子类run()方法
2、Runnable:通过Thread构造将Runnable引用传给Thread成员变量target(其中是通过init()方法传的),然后run()方法会有判断不为null就调用了成员变量(Runnable引用)的run()方法
3、使用Thread类操作多线程的时候无法达到资源共享的目的,而使用Runnale接口实现的多线程操作可以实现资源共享;
- 两种方式的好处和坏处
继承Thread的好处:可以直接使用Thread类中的run()方法,代码简单;
继承Thread的坏处:如果类已经有了父类,就不能使用此方法,因为JAVA是单继承
实现Runnable接口的好处:尽管自己定义的类有了父类,也可以使用线程,因为有了父类也可以实现接口;而且还可以多实现,资源共享,代码程数据是独立的 ;
实现的Runnable的坏处:不能直接继承Thread的run()方法,得通过创建Thread类有参构造对象将实现Runnable的类引用传给有参Thread构造,然后调用该Thread类对象start()方法开启线程;
匿名内部类实现多线程两种方式
public class Demo {
public static void main(String[] agrs) {
//第一种继承Thread
new Thread() {
public void run() {
语句;
}
}.start();
//第二种实现Runnable
new Thread(new Runnable() {
public void run() {
语句;
}
}).start();
}
}
- 获取和设置线程名称(getName&setName)
1、通过getName()方法获取线程名称
2、通过构造和setName()可以设置线程名称
代码如下:
public class Demo {
public static void main(String[] agrs) {
///通过构造设置线程名称
new Thread("线程1") {
public void run() {
System.out.println(this.getName());
}
}.start();
//通过setName()设置线程名称
Thread t = new Thread() {
public void run() {
System.out.println();
}
};
t.setName("线程2");
System.out.println(t.getName());
}
}
获取当前执行线程的引用(currentThread)
public class Demo6_Thread {
public static void main(String[] agrs) {
new Thread() {
public void run() {
System.out.println(getName());
}
}.start();
new Thread(new Runnable() {
public void run() {
//通过类名点调用currentThread();获取当前执行线程对象的引用;
System.out.println(Thread.currentThread().getName());
}
}).start();
//获取主线程的引用,也可以设置
System.out.println(Thread.currentThread().getName());
}
}
休眠线程(sleep)
- Thread.sleep(long millis);静态方法,此方法休眠当前执行线程,指定时间继续执行;类名点调用; 1秒 = 1000毫秒;
public class Demo7_Sleep {
public static void main(String[] agrs) {
new Thread() {
public void run() {
while(true)
SimpleDateFormat sdf = new SimpleDateFormat(
"yyyy年MM月dd日 HH:mm:ss");
System.out.println(sdf.format(new Date()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
}
守护线程(setDaemon)
- setDaemon(),设置一个线程为守护线程,那么该线程不会单独执行,当其它线程非守护线程执行结束后,就会自动退出;
public class Demo9_SetDaemon {
public static void main(String[] agrs) {
new Thread() {
public void run() {
for(int i = 0; i < 2; i++) {
System.out.println(getName() + " : 你好");
}
}
}.start();
Thread t = new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread() +
" : 不好" + i);
}
}
});
t.setDaemon(true);
t.start();
}
}
加入线程(join)
- join():当前线程暂停,等到指定线程执行完,当前线程继续执行;
- join(long millis):当前线程暂停,等待指定线程执行指定时间后,当前线程继续执行;
public class Demo10_JoIn {
public static void main(String[] agrs) {
//注意如果匿名内部类想访问他所在方法中的局部变量时,该局部变量必须用final修饰;
final Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(i + "我是加入线程 : " + getName());
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
if(i == 5) {
try {
t1.join(10);
//该线程指定执行时间1秒;过了时间后两条线程交替执行;
//t1.join(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println( i + "我是线和 : " + getName());
}
}
};
t1.start();
t2.start();
}
}
礼让线程
- yield()方法让出cpu
public class Demo11_YieLd {
public static void main(String[] agrs) {
new MyThread().start();
new MyThread().start();
}
}
class MyThread extends Thread {
public void run() {
for(int i = 0; i < 1000; i++) {
if(i % 10 == 0) { //当是10的倍数时
this.yield(); //让出cpu
}
System.out.println(getName() + " : " + i):
}
}
}
线程的优先级
- setPriority(int newPriority)方法:设置线程优先级;
- newPriority:最大取值10,最小取值1;
- getPriority()方法获取当前线程优先级
- main线程优先级是:5
public class Demo12_SetPriority {
public static void main(String[] agrs) {
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(getName() + " : " + i);
}
}
}.start();
Thread t2 = new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread() + " : " + i);
}
}
}).start();
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();
}
}
判断线程是否启动
- 在JAVA中可以使用isAlive()方法来测试线程是否已经启动而且仍然在启动;
public class Demo_IsAlive {
public static void main(String[] agrs) {
Thread t = new Thread() {
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println("线程运行 i " + i);
}
}
};
System.out.println("启动前 : " + t.isAlive());
t.start();
System.out.println("启动后 : " + t.isAlive());
for(int i = 0; i < 3; i++) {
System.out.println("main运行 " + i);
}
System.out.println("启动前 : " + t.isAlive());
}
}
中继线程
- interrupt():中断当前线程
public class Demo_Interrupt {
public static void main(String[] agrs) {
Thrread t = new Thread() {
public void run() {
System.out.println("进入run()方法");
try{
Thread.sleep(1000);
System.out.println("休眠结束");
}catch(InterrruptedException e) {
System.out.println("休眠被终止");
return;
}
System.out.println("run()方法正常结束");
}
};
t.start();
t.interrupt();//线程中断执行
}
}
案例
/**
*要求:设计一个线程类,要求产生三个线程对象,并可以分别设置三个线程的休眠时间,如下所示
*线程a:休眠10秒
*线程b:休眠20秒
*线程c:休眠30秒
* @author MAC
*
*/
public class Test6 {
public static void main(String[] agrs) {
//demo1();
MyRunnableTest mrt = new MyRunnableTest(10, "线程a");
Thread t1 = new Thread(mrt,mrt.getNameTest());
t1.start();
}
private static void demo1() {
MyThread m1 = new MyThread("线程a",10);
MyThread m2 = new MyThread("线程b",20);
MyThread m3 = new MyThread("线程c",30);
m1.start();
m2.start();
m3.start();
}
}
//继承Thread
class MyThread extends Thread {
private long time;
public MyThread(String name,long time) {
super(name);
this.time = time;
}
public void run() {
try {
Thread.sleep(this.time * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.currentThread().getName() + " : 休眠了" + this.time + "秒");
System.out.println(this.currentThread().getName() + " : 开始执行了");
}
}
class MyRunnableTest implements Runnable {
private int time;
private String name;
public MyRunnableTest(int time,String name) {
this.time = time;
this.name = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(this.time * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : 休眠了" + this.time + "秒");
System.out.println(Thread.currentThread().getName() + " : 开始执行了");
}
public String getNameTest(){
return this.name;
}
}
多线程的同步代码块
- 同步代码块:使用synchronized关键字加上一个锁对象来定义一块代码,这就叫同步代码块;多个同步代码块如果使用相同锁对象,那么他们就是同步的;
- 什么情况下使用同步代码块:当多线程并发时,有多块代码同时执行时,我们希望某一块代码执行过程中,CPU不要切换到其他线程工作,这时就需要同步;如果两块代码是同步的,那么在同一时只能执行一块,在一块代码没执行结束之前,不会执行另一块
public class Demo3_Synchronized {
public static void main(String[] agrs) {
final PrinterTest pt = new PrinterTest();
new Thread() {
public void run() {
pt.print1();
}
}.start();
new Thread() {
public void run() {
pt.print2();
}
}.start();
}
}
class PrinterTest {
Object o = new Object();
public void print1() {
synchronized(o) { //注意不能是匿名内部类
for(int i = 0; i < 1000; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print("李");
System.out.print("超");
System.out.print("武");
System.out.print("\r\n");
}
}
}
public void print2() {
synchronized(o) {
for(int i = 0; i < 1000; i++) {
System.out.print("陈");
System.out.print("清");
System.out.print("香");
System.out.print("\r\n");
}
}
}
}
多线程的同步方法
- 使用synchronized修饰的方法,该方法中所有的代码都是同步的;
- 关于锁:修饰非静态方法,该同步代码块的锁对象是this;修改静态方法,该同步代码块的锁对象是类名.class(字节码对象);
public class Demo4_Synchronized {
public static void main(String[] agrs) {
final Printer3 p3 = new Printer3();
new Thread() {
public void run() {
p3.print1();
}
}.start();
new Thread() {
public void run() {
p3.print2();
}
}.start();
}
}
class Printer3 {
public synchronized void print1() {
for(int i = 0; i < 1000; i++) {
System.out.print("李");
System.out.print("超");
System.out.print("武");
System.out.print("帅");
System.out.print("哥");
System.out.print("\r\n");
}
}
public void print2() {
synchronized(this) {
for(int i = 0; i < 1000; i++) {
System.out.print("陈");
System.out.print("清");
System.out.print("香");
System.out.print("好");
System.out.print("看");
System.out.print("\r\n");
}
}
}
}
线程安全问题
- 多线程并发操作同一数据时,就可能会出现线程安全问题;
- 使用同步技术可以解决该问题,把操作数据的代码进行同步;不要多个线程一起操作
- 例如:买票、有100张票,但有四个线程同时买票,如果不同步就会出现,400张票的效果;
public class Test {
public static void main(String[] agrs) {
new Ticket().start();
new Ticket().start();
new Ticket().start();
new Ticket().start();
}
}
class Ticket extends Thread {
private static int ticket = 100; //100张票
public void run() {
while(true) {
synchronized(Ticket.class) {
if(ticket == 0) {
System.out.println("票买完了");
}
System.out.println(getName() + " : " + ticket--);
}
}
}
}
多线程死锁问题
- 什么是死锁:就是两个线程都在等待对方执行完毕才能继续执行下去,结果就是两个线程都在无限等待中,这就是死锁;
- 多线程同步时,如果同步代码嵌套,使用相同锁对象,就可能出现死锁这种情况
- 代码示例(尽量不要使用同步嵌套)
public class Demo5_DeadLock {
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] agrs) {
new Thread() {
public void run() {
while(true) {
synchronized(s1) {
System.out.println(getName() + "拿到" + s1 + "等待" + s2);
synchronized(s2) {
System.out.println(getName() + "拿到" + s2 + "开吃");
}
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
synchronized(s2) {
System.out.println(getName() + "拿到" + s2 + "等待" + s1);
synchronized(s1) {
System.out.println(getName() + "拿到" + s1 + "开吃");
}
}
}
}
}.start();
}
}
1、资源共享时需要同步操作
2、程序中过多的使用同步会产生死锁;
Collections.synchronizedxxx
- API中有一些线程不安全的类,如ArrayList,HashMap,StringBuilder;我们可以通过Collections.synchronizedXXX将线程不安全的线程转换为线程安全的;
单例设计模式
- 什么是单例设计模式:就是一个类只能有一个对象;
- 如果保证类在内存只能有一个对象呢?
1、控制类的创建,不能让其它类创建本类对象(私有构造)
2、在本类中定义一个本类对象。
3、提拱静态、公共的访问方法。并将2中创建的本类对象引用返回出去,让其它类都可以拿该引用,那么其它类其实拿的是同一个引用,也就只有一个对象;
public class Demo4_Singleton {
public static void main(String[] agrs) {
}
}
//饿汉式
class Singleton1 {
private Singleton1(){};
private static Singleton1 s = new Singleton1();
public static Singleton1 getInstance() {
return s;
}
}
//懒汉式
class Singleton2 {
private Singleton2(){};
private static Singleton2 s;
public static Singleton2 getInstance() {
if(s == null) {
s = new Singleton2();
}
return s;
}
}
//第三种和饿汉式类似
class Singleton3 {
private Singleton3(){};
public static final Singleton3 s = new Singleton3();
}
- 饿汉式和懒汉式的区别
答:饿汉式是空间换时间,懒汉式是时间换空间;
在多线程访问时,饿汉式不会创建多个对象,懒汉式有可能会创建多个对象
Java.long.Runtime(运行时类,运用单例设计模式)
public class Demo_Runtime {
public static void main(String[] agrs) {
Runtime r = Runtime.getRuntime(); //获取运行时对象
r.exec("shutdown -s -t 300"); //该方法在单独的进程中执行指定的字符串命令,这里命令的意思是在指定时间关机
r.exec("shutdown -a"); //关闭命令
}
}
两个线程间的通信
- 什么时候需要通信?
答:多个线和并发执行时,在默认情况下CPU是随机切换的,如果我们希望它们有规则的执行,就可以使用通信;例有每个线程执行一次打印 - 怎么通信?
答:如果希望线程等待,就调用wait()方法 Object中的方法
如果希望唤醒等待的线程,就调用notify()方法,也是Objetc中的方法
这两个方法必须在同步代码块中,并且使用同步锁对象来调用;
//等待唤醒机制
public class Demo1_Notify {
public static void main(String[] agrs) {
//jdk1.8后内部类访问局部变量,不用加final;已经默认给你隐藏了
final Printer p = new Printer();
new Thread() {
while(true) {
p.print1();
}
}.start();
new Thread() {
while(true) {
p.print2();
}
}.start();
}
}
class Printer {
private int flag = 1;
public void print1() {
synchronized(this) {
if(flag != 1) {
try {
this.wait(); // 该方法使当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
flag = 2;
this.notify(); //随机唤醒单个等待线程
}
}
publuc void print2() {
synchronized(this) {
if(flag != 2) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
flag = 1;
this.notify();
}
}
}
生产者和消费者
public class ThreadCaseDemo01 {
public static void main(String[] agrs) {
Info info = new Info();
Producer pro = new Producer(info);
Consumer con = new Consumer(info);
new Thread(pro).start();
new Thread(con).start();
}
}
class Info { //定义信息类
private String name = "李超武";
private String content = "Java开发工程师";
private boolean flag = false;
public synchronized void set(String name,String content) {
if(!flag) {
try {
super.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.content = content;
flag = false;
super.notifyAll();
}
public synchronized void get() {
if(flag) {
try {
super.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name + "-->" + this.content);
flag = true;
super.notifyAll();
}
}
//生产者
class Producer implements Runnable {
private Info info = null;
public Producer(Info info) {
this.info = info;
}
public void run() {
boolean flag = false;
for(int i = 0; i < 50; i++) {
synchronized(Producer.class) {
if(flag) {
this.info.set("李超武","Java开发工程师");
flag = false;
}else {
this.info.set("陈清香","睡觉");
flag = true;
}
}
}
}
}
//消费者
class Consumer implements Runnable {
private Info info = null;
public Consumer(Info info) {
this.info = info;
}
public void run() {
for(int i = 0; i < 50; i++) {
info.get();
}
}
}
本程序总结:
1、在本程序操作中需要以下两点问题
生产者要不停的生产,但不能生产错误的信息、重复生产等;
消费者不停取走,但不能重复取走;
2、Object中对线程的支持
等待:wait();
唤醒:notify()、notifyAll()
3、本程序只是假设对于同步,等待,唤醒机制的操作,本来运行的问题了解就可以了
三个及三个以上的线程通信
- 多个线程通信的问题
notify()方法是随机唤醒第一个等待的线程
notifyAll()方法是唤醒所有等待的线程,哪个优先级高,哪个线程就有可能先执行;
注意:要使用while进行判断标记,不能使用if;因为if是在哪等待就在哪唤醒,而while是重新进行判断
//这里我只写核心代码
class Printer {
private int flag = 1;
public void print1() {
synchronized(this) {
while(flag != 1) {
this.wait(); //这里要处理异常,由于篇幅没有处理
}
System.out.print("李");
System.out.print("超");
System.out.print("武");
System.out.print("\r\n");
flag = 2;
this.notifyAll();
}
}
public void print2() {
synchronized(this) {
while(flag != 2) {
this.wait();
}
System.out.print("陈");
System.out.print("清");
System.out.print("香");
System.out.print("\r\n");
flag = 3;
this.notifyAll();
}
}
public void print3() {
synchronized(this) {
while(flag != 3) {
this.wait();
}
System.out.print("吴");
System.out.print("非");
System.out.print("凡");
System.out.print("\r\n");
flag = 1;
this.notifyAll();
}
}
}
- 补充说明(注意)
1、在同步代码块中:你的锁对象是什么,就用该对象调用wait()、notify()、notifyAll();
2、为什么wait()、notify()方法定义在Object?很明显因为锁对象可以是任意对象,而任意对象必须都可以调用wait()等方法,所以定义在Object中,所有类都继承了Object类
3、sleep()方法和wait()方法的区别?
a、sleep()必须传入时间值参数,时间到了自动醒来
wait()可以传入时间值参数(和sleep()一样),也可以不传入参数(就处于一直等待,等待被唤醒)
b、sleep()在同步方法和同步代码块中,不释放锁;就是不释放执行权;sleep开始、休眠过程中、结束后都有执行权、执行的还是当前线程;而wait()方法一旦执行,就是放弃了执行权,别的线程可以执行,被唤醒之后,才有执行权;也就是释放锁;
互斥锁
- 什么是互斥锁:使用ReentrantLock类的lock()和unlock()方法进行同步
- 怎么使用?
答:使用ReentrantLock类的newCondition()方法可以获取Condition对象,需要使线程等待的时候使用Condition的await()方法,要唤醒线和时signal()方法(也有signalAll()方法);不同的线程使用不同Condition对象,这样就可以区分唤醒的时候找的是哪个线程; - 类和主要方法介绍
ReentrantLock类:具有与使用synchronized方法相同的功能;而且功能更加强大
ReentrantLock中lock()方法:获取锁
ReentrantLock中unlock()方法:释放锁
ReentrantLock中newCondition()方法:获取一个的监视器;
Condition接口:理解监视器
Condition接口中方法里面都是抽象的子类必须实现,其实在通过newCondition()方法获取对象时,获取的是new ConditionObject();该类重写了Condition接口中所有的抽象方法;
Condition中await()方法:帮助文档中说导致当前线程等到发信息或interrupted,其实在初学者看来就是使用线程等待,和Objetc类中wait()方法一样;
Condition中方法signal()方法:帮助文档说唤醒一个线程;和Object类中notify()方法一样;
它俩区别:singnal()可以指定哪个线程唤醒,而notify()方法随机唤醒正在等待的线程;
其实它目前用不到自己帮助文档和源码;
//核心部分
class Printer {
private ReentrantLock r = new ReentrantLock(); //创建一个互斥锁对象;
priavte Condition c1 = r.newCondition(); //获取监视器
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
private int flag = 1;
public void print1() {
r.lock(); //获取锁
if(flag != 1) {
c1.await(); //监视器1 等待. 此处有异常由于篇幅没有写要注意
}
System.out.print("李");
System.out.print("超");
System.out.print("武");
System.out.print("\r\n");
flag = 2;
c2.signal(); //唤醒监视器2所在线程
r.unlock(); //释放锁
}
public void print2() {
r.lock();
if(flag != 2) {
c2.await();
}
System.out.print("陈");
System.out.print("清");
System.out.print("香");
System.out.print("\r\n");
flag = 3;
c3.signal();
r.unlock();
}
public void print3() {
r.lock();
if(flag != 3) {
c3.await();
}
System.out.print("吴");
System.out.print("非");
System.out.print("凡");
System.out.print("\r\n");
flag = 1;
c1.signal();
r.unlock();
}
}
线程组的使用
- 线程组:java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制;
- 默认情况下所有线程都是主线程组main
- 方法:getThreadGroup();方法 通过线程对象调用该方法,获取该线程所在线程组对象
- 方法:getName();通过线程组对象获取该线程组名称;
- 我们可以自己创建线程组和设置
ThreadGroup tg = new ThreadGroup(“线程组”);//创建新线程组
然后通过Thread构造将线程放到组里去并设置名称
Thread(tg,线程对象,线和名称);
然后可以将该组中所有的线程设置为守护线程和优先级;
public class Demo_ThreadGroup {
public static void main(String[] agrs) {
//创建新的线程组
ThreadGroup tg = new ThreadGroup("线程组");
//创建Runnable子类对象
MyRunnable mr = new MyRunnable();
//将t1放到线程中
Thread t1 = new Thread(tg,mr,"张三");
//将t2放到线程组中
Thread t2 = new Thread(tg,mr,"李四");
//获取t1的线程组名称
System.out.println(t1.getThreadGroup().getName());
//将线程组下所有线程设置成守护线程;
tg.setDaemon(true);
}
}
class MyRunnable implements Runnable {
public void run() {
语句;
}
}
线程的五种状态
- 新建、就绪、运行、阻塞、死亡;
- 注意:从这可以知道,调用start()方法不是立刻就启动,而是等cpu进行调度;
线程池
- 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能;特别是当程序中要创建大量生存周期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程执行结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用,jdk1.5前,我们必手动实现自己的线程池,从jdk1.5开始,java内置支持线程池;
- 内置线程池的使用
JDK1.5新增了一个Executors工厂类来产生线程池,有如下方法
//1、创建线程池,指定里面几条线程
public static ExecutorService newFixedThreadPool(int nThreads)
//2、创建线程池里面就一个条
public static ExecutorService newSingleThreadExecutor()
//3、通过返回的ExecutorService对象将线程添加到线程池中,并执行
Future<?> submit(Runnable task); //重载的方法
//4、关闭线程池
void shutdown();
public class Demo_Exeutors {
public static void main(String[] agrs) {
//创建线程池指定为两条线程
ExecutorsService pool = Executors.newFixedThreadPool(2);
pool.submit(new MyRunnable()); //将线程添加线程池中并执行
pool.submit(new MyRunnable());
pool.shutdown();//关闭线程池
}
}
class MyRunnable Implements{
public void run() {
语句;
}
}
多线程的第三种实现方式(Callable)
//求 100和50的和
public class Demo_Callable {
public static void main(String[] agrs) throws Exception{
ExecutorService pool = Executors.newFixedThreadPool(2); //创建线程池
Future<Integer> t1 = pool.submit(new MyCallable(100));//添加到线程池中并执行并返回Future
Future<Integer> t2 = pool.submit(new MyCallable(50));
System.out.println(t1.get());//输出返回值
System.out.println(t2.get());
pool.shutdown();//关闭线程
}
}
class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
public Integre call() throws Exception{
int sum = 0;
for(int i = 1; i < num; i++) {
sum += i
}
return sum;
}
}
线程的生命周期
- 一个新的线程创建之后通过start()方法进入运行状态,在运行状态中可以使用yield()进行礼让,但线程并没有停止,如果一个线程需要暂停的放,可以使用wait()、sleep()、await()、suspend(),如果现在线程不需要再执行,则可以通过stop()方法结束,还有就是等run()方法执行完毕也表示结束;
- 以下方法被弃用
1、suspend():暂时挂起线程
2、resume():恢复挂起的线程
3、stop():停止线程
**为什么被弃用?**答:都会产生死锁的问题
如果我想停止一个线程如何做(stop()被弃用)
-
通过设置标志位
-
代码实现
public class Demo {
public static void main(String[] agrs) {
MyThread mt = new MyThread();
Thread t = new Thread(mt,"线程");
t.start();
Thread.sleep(100);//此处有异常
mt.stop();
}
}
class MyThread implements Runnable {
private boolean flag = true;
public void run() {
int i = 0;
while(this.flag) {
System.out.println(Thread.currentThread().getName() +
" 运行,i = " + (i++));
}
}
public void stop() {
this.flag = false;
}
}
简单工厂设计模式
- 简单工厂设计模式:又叫静态方法模式,它定义一个具体的工厂类负责创建一些类的实例
- 优点:客户端不需要在负责对象的创建,从而明确了各个类的职责;
- 缺点:这个工厂负责所有类的创建,如果有新的类增加,某些类的创建方式不同,这时就要不停的修改;不利于维护
- 案例:
//动物类
public abstract class Animal {
public void eat();
}
//狗类
public class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
}
//猫类
public class Catextends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
//工厂类
public class AnimalFactory {
private AnimalFactory(){};
public static Animal createAnimal(String name) {
if("Dog".equals(name)) {
return new Dog();
}else if("Cat".equals(name)) {
return new Cat();
}else {
return null;
}
}
}
//测试类
public class Test {
public static void mian(String[] agrs) {
Dog d = (Dog)AnimalFactory.createAnimal("Dog");
Cat c = (Cat)AinmalFactory.createAnimal("Cat");
d.eat();
c.eat();
}
}
工厂方法设计模式
- 工厂方法设计模式:工厂方法模式中抽象工厂类负责创建类的接口,具体对象的创建工厂由继承抽象工厂的具体类实现。
- 优点客户端不需要在负责对象的创建,从而明确了各个类的职责。如果新的类增加了,只需要增加一个具体的类和具体类的工厂类就可以了,不影响已有代码,后期维护容易;增强了系统的扩展性;
- 缺点需要额外的编写代码,增加了工作量;
- 案例
//动物抽象类
public abstract class Animal {
public abstract void eat();
}
//猫类
public class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
//狗类
public class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
}
//抽象工厂类
public interface Factory {
public Animal createAnimal();
}
//猫工厂类
public class CatFactory implements Factory{
public Animal createAnimal() {
return new Cat();
}
}
//狗工厂类
public class DogFactory implements Factory {
public Animal createAnimal() {
return new Dog();
}
}
一些面试题
- Thread类中的start()和run()方法有什么关系?
答:start()方法用来启动线程,run()方法内放了要执行的代码,用来被执行,start()低层其实调用run()方法,看源码得知,如果你是直接调用run()方法,就是等于在主线程调用了该方法而已,并没有创建新线程,而通过start()才是启动新线程并在新线程中执行run()方法;
- 进程和线程的区别?
答:进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程;线程消失了,进程不会消失,而进程消失了,线程就消失了
- 同步代码块和同步方法的区别是什么?
答:同步方法是修饰方法通过synchronized修饰,而同步代码块是在方法内部进行定义;同步方法是同步了整个方法,而同步代码块方法中可以同步某块代码;
- 什么是死锁?
答:两个线程都在等待对方执行完毕才能继续执行下去,这就是死锁,结果就是两个线程都处于无限等待中;