多线程知识总结
目录:
1.概念
2.进程和线程的区别
3.实现代码
4.模拟卖火车票实现多线程资源的抢夺
5.线程的2种实现方式
6.线程的生命周期
7.线程的状态
8.线程安全
9.死锁
10.线程通信
11.notify
**正文**
1.概念:线程是进程中的一个个的任务和功能模块的任务执行路径,如同qq中的聊天发送信息和发送图片,扫描二维码都是线程。
2.线程和进程:
2.1 线程:进程由线程来实现功能的完成,进程可以是并发的,可以并发执行;
进程中至少有一个线程,线程中最先被执行的是main线程;
单核计算机只有一个线程;
进程中的线程资源共享;
2.2 进程:进程间的资源不共享,
进程可以并发进行;比如:可以同时用英雄联盟打游戏+用网易云听音乐,这2者间资源不共享,但是可以同时进行。
一个进程中至少有一个存活的线程,否则进程结束。
3.线程的2种实现方式:
1》继承thread
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
for(int i=0;i<10;i++){
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
//线程是用来完成某些任务的,那么任务的主体部分就在run 方法内部。
// 缺点:不能再继承其他的类,多个线程访问同一个数据的时候,处理相对复杂。要么单例,要么持有唯一对象的引用。
class MyThread extends Thread{
public void run() {
for(int i=0;i<10;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
2》实现runnable接口
建议这种方式,因为java是单继承,多实现,所以尽量实现接口,少用继承
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//先创建Runnable 的实例
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target);
thread.start();
}
class MyRunnable implements Runnable{
//线程的执行的主体
public void run() {
for(int i=0;i<10;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
4.线程的实现原理
cpu是如何处理这么多进程的任务的请求呢?
cpu 将执行的过程细分了非常微小的时间片。然后这些时间片会分配给不同的进程执行。
因为时间片非常短,所以宏观的感受就是所有的进程都是一起执行的。但是微观上来说,所有的进程都是线性执行的。
线程是cpu调度执行的最小单位,进程是cpu分配资源的最小单位。
cpu同时执行多个线程和进程,分配内存资源和实现任务调度。
4.模拟卖火车票实现多线程资源的抢夺
public class Test1 {
public static void main(String[] args) {
TicketThread tr = new TicketThread();
Thread t = new Thread(tr);
t.setName("窗口一");//3个窗口同时卖票可以共享100张票
Thread t2 = new Thread(tr);
t2.setName("窗口二");
Thread t3 = new Thread(tr);
t3.setName("窗口三");
t.start();
t2.start();
t3.start();
}
static class TicketThread implements Runnable {
int ticket = 100;
Object obj = new Object();
@Override
public void run() {
//method(); //method是实现了同步方法,可以解决火车票出现的问题,
while (true) {
synchronized (obj) { //加锁避免出现异常
if (ticket > 0) {
try {
Thread.sleep(100); //每个线程之间睡眠一段时间,避免阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket--);
}
}
}
}
}
}
6.线程的生命周期
线程包含:新建(new)、准备就绪(runnable)、运行(run)、阻塞(blocking)、终止销毁(terminate)
生命周期图如下:
6.1
》》》Thread t = new Thread(); ------- 新建状态
》》》t.start(); ------ 运行状态‘’
》》》 t.sleep(3000); ----block状态,需要notify或者notifyall方法重新启动线程
》》》阻塞式方法:
Scanner nextDouble nextFloat next() nextLine(); readLine();
》》》线程结束、jvm崩溃、线程异常—销毁状态
6.2 详细讲解---- 线程的生命周期包括哪几个阶段?(搜索文章)
原创: Java蚂蚁 Java蚂蚁 7月22日
7.线程的状态
7.1 priority优先级:
1》一个线程创建之后,如果不指定线程的优先级,则线程的优先级为5;
2》访问线程对象的优先级:getPriority()
设置线程对象的优先级:setPriority(int priority) priority取值范围为[1-10]
3》线程最大的优先级为10,最小优先级为1,默认为5.
线程优先级越大,被cpu调度执行的概率越高,反之越小(只是被调度执行的概率比较小。不是不调度执行)。
设置优先级的时候,不要超过范围[1-10],不然会抛出异常。
4》可以在线程启动之前设置优先级,也可以在启动之后设置。
5》设置优先级:
PriorityThread thread = new PriorityThread();
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
7.2 join 线程插队
导致当前线程被阻塞,thread实现了一个插队的效果,需要插队的thread 被优先执行了。
Thread thread = new Thread(new JoinRunnable());
thread.start();
thread.join(1);
//导致当前线程被阻塞,thread实现了一个插队的效果,thread 被优先执行了。
//插队的线程执行完毕之后,被阻塞的线程才会解除阻塞,进入就绪状态,等待cpu的调度。
7.3 Sleep(long millis):线程休眠,进入阻塞
作用:导致当前线程进入阻塞状态(线程休眠),被阻塞的时间为传入的参数的值。一旦时间到达指定的时间,那么被阻塞的线程转换为就绪状态。
Thread.sleep(1000);
7.4 yield
yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。
出让当前cpu的控制权,让cpu重新为线程分配资源。先就绪后启动。
Thread.yield();
7.5
线程的分类:可以被分为两类:
1:用户线程=前台线程。
2:守护线程=后台线程
区别:
》》》进程的生命周期只依赖于进程内部的用户线程。如果一个进程中所有的用户线程都死亡了,那么该进程就被jvm杀死了。
》》》如果一个进程中存活的线程只有守护线程了,那么jvm会将进程杀死,进程内部的守护线程一并被杀死了。
》》》main 线程可以为守护线程么?不可以的。主线程是jvm 启动的。不能再设置为守护线程。
DaemonThread thread = new DaemonThread();
//设置thread 为守护线程。
thread.setDaemon(true);
thread.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName());
Thread.sleep(200);
}
8.线程安全:
》》》线程安全问题:指的是多线程的环境的下,多个线程同时访问同一个数据,可能产生数据不同步的问题。
》》》ArrayList 是线程不安全的。
A 线程对 ArrayList 进行添加元素 操作。
B 线程对 ArrayList 进行遍历元素的操作。
》》》解决方法
针对线程安全的解决方案:
1:同步代码块
语法:
synchronized (同步监视器){
//同步代码块的内容
}
》》》工作原理解析:
工作的过程:
1:张三先取钱,发现同步监视器 没有上锁,那就对 同步监视器 上锁(添加了一个标识)。并访问同步代码块的内容。
2:张三媳妇来取钱,发现同步监视器 被张三上锁。 只能在 监视器对象上等待(就绪状态)。
3:张三把钱取走了,出了同步代码块,同时将同步监视器解锁。
4:等到cpu 调度执行张三媳妇的时候,发现同步监视器 被解锁了,那么张三媳妇就对同步监视器 加锁并访问同步代码
》》》避免对同一个账户同时进行数据的操作,导致数据丢失或者异常,一般在数据库中常见这个问题,如果数据库事务不加上回滚就会出现这个不同步的问题,导致账户金额数据错乱。
类似于人上厕所,锁隔断小门的过程。
没人,进去锁门,然后开锁,出门。别人看到锁着就等待,。。。
线程安全代码演示:
public class SyTest {
public static void main(String[] args) {
Account account = new Account();
Person zhansan = new Person(account, "张三");
Person lisi= new Person(account, "李四");
zhansan.start(); //张三可以取到钱,取完后卡里余额为500
lisi.start(); //李四无法取钱,因为卡里余额不足
}
}
//唯一的账户类
class Account{
private int balance = 1500;//余额
//取钱的方法
public void withDraw(int money){
***synchronized (Account.class) {***
if(balance >= money){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= money;
System.out.println(Thread.currentThread().getName()+"取钱成功!余额为:"+balance);
}else{
System.out.println(Thread.currentThread().getName()+"取钱失败!余额不足,余额为:"+balance);
}
}
}
}
class Person extends Thread{
private static Object o = new Object();
private Account account;
public Person(Account account,String name) {
this.account = account;
setName(name);
}
@Override
public void run() {
// synchronized (Person.class) {
account.withDraw(1000);
// }
}
}
9.死锁–deadlock
具体讲解:https://www.jianshu.com/p/a90803bb0ab7
地址讲解2:https://www.cnblogs.com/bopo/p/9228834.html
》》》举例:
A拿遥控器,锁住,不给B,但是想要B手中的电池;
B拿电池,锁住,不给B,但是想拿A手中的遥控器;
最终都拿不到这个组合资源,实现让空调启动的功能,双层套锁,形成互相制衡,彼此成为彼此的解锁条件。
public void run() {
if(id == 0){ //线程-1
synchronized(o1){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//TODO
System.out.println(Thread.currentThread().getName()+"---o1--->o2");
synchronized(o2){
System.out.println(Thread.currentThread().getName()+"---o1--->o2---");
}
}
}else{ //线程-2
//线程-2 锁住了o2,正在请求锁o1
synchronized(o2){ //TODO
System.out.println(Thread.currentThread().getName()+"---o2--->o1");
synchronized(o1){
System.out.println(Thread.currentThread().getName()+"---o2--->o1---");
}
}
}
》》》出现的环境:同步嵌套锁(同步监视器),多个线程。尽量避免。
》》》如何避免:
1.创建线程安全序列
》》》安全状态:如果系统存在 由所有的安全序列{P1,P2,…Pn},则系统处于安全状态。一个进程序列是安全的,如果对其中每一个进程Pi(i >=1 && i <= n)他以后尚需要的资源不超过系统当前剩余资源量与所有进程Pj(j < i)当前占有资源量之和,系统处于安全状态则不会发生死锁。
》》》不安全状态:如果不存在任何一个安全序列,则系统处于不安全状态。他们之间的对对应关系如下图所示:
10.线程通信—多个线程之间进行信息的收发。
public class PipedStreamTest {
public static void main(String[] args) throws Exception {
PipedOutputStream pos = new PipedOutputStream();
//管道输入流和管道输出流,建立关联的方式,通过构造方法,通过对象提供的方法。
PipedInputStream pis = new PipedInputStream(/*pos*/);
pis.connect(pos);
SendThread thread =new SendThread(pos);
ReceiveThread thread2 =new ReceiveThread(pis);
thread2.start();
Thread.sleep(5000);
thread.start();
}
}
//是用来发送数据,
//发送:小鱼儿。
class SendThread extends Thread{
private PipedOutputStream pos;
public SendThread(PipedOutputStream pos) {
this.pos = pos;
}
//System.in InputStreamReader +BufferedReader
//通过键盘输入,将输入的内容,源源不断的发送给 接收方。 输入bye 的时候结束输入。
@Override
public void run() {
System.out.println("SendThread.run()");
String str = "小鱼儿,你还好么,希望你好好的!";
try {
// pos.write(str.getBytes());
// pos.close();
send();
} catch (Exception e) {
e.printStackTrace();
}
}
//System.in InputStreamReader +BufferedReader
//通过键盘输入,将输入的内容,源源不断的发送给 接收方。 输入bye 的时候结束输入。
private void send() throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = null;
while((str = br.readLine())!= null){
pos.write(str.getBytes()); //str.getBytes()编码
pos.flush();
if("bye".equals(str))
break;
}
pos.close();
}
}
class ReceiveThread extends Thread{
private PipedInputStream pis;
public ReceiveThread(PipedInputStream pis) {
this.pis = pis;
}
@Override
public void run() {
System.out.println("ReceiveThread.run()");
byte[] buf = new byte[40];
try {
//输入流,如果输出流没有数据提供,那么就一直等待。
// int count = pis.read(buf);
// String str = new String(buf,0,count);
// System.out.println("收到的信息=="+str);
receive();
} catch (Exception e) {
e.printStackTrace();
}
}
private void receive() throws Exception{
byte[] buf = new byte[1024];
int count = 0;
while((count = pis.read(buf))!= -1){ //有消息就接收
String str = new String(buf,0,count);
//把接收到的消息转换成字符串,编码解码的过程
System.out.println("收到的信息=="+str);
if("bye".equals(str))
break;
}
}
}
11.notify
notify():
使用环境:必须在同步代码块或者同步方法内使用。
作用:导致唤醒在当前对象上等待的某个线程,进入就绪状态。
notifyAll():
使用环境:必须在同步代码块或者同步方法内使用。
作用:导致唤醒在当前对象上等待的所有线程,进入就绪状态。