java多线程相关
1:多线程与多进程的概念
一个进程是一个应用程序(一个进程是一个软件)
线程是一个进程中执行的场景/执行的单元
一个进程可以启动多个线程
对于java程序来说,当dos命令窗口输入:
java HelloWold 回车之后
会先启动 JVM 而JVM本身就是一个主进程
JVM会再启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发
注意:进程A与进程B的内存独立不共享
线程A与线程B堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈
使用的多线程后 main方法结束,程序也有可能不会结束。
main方法结束只是主线程结束了,主栈空了,其他栈的线程可能还在压栈弹栈。
对于单核CPU只有一个大脑,不能够做到多线程并发,但可以给人做到一种多线程并发的感觉
2:Java中实现多线程的两种方法
1)编写一个类,直接继承java.lang.Thread,重写run方法
start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这句代码执行完任务之后,瞬间就结束了,只是为了开辟新的栈空间,只要新的栈内存空间被开辟出来,start()方法就结束了。
启动成功的线程会自动去调用run方法,并且run方法在分支栈的底部(压栈)main方法在主栈的底部。run方法和main方法是平级的。多一个分支线程
直接调用run()方法,不会启动线程,不会分配新的分支栈(依旧是单线程执行)
2)编写一个类,实现java.lang.Runnable接口
new Thread(new 类名)
匿名内部类
public class Test03 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
});
thread.start();
for (int i=0;i<10;i++){
System.out.println(i);
}
}
}
3:线程的生命周期
注意:
Thread thread = Thread.currentThread(); //the currently executing thread.
System.out.println(thread.getName());
Returns a reference to the currently executing thread object.
对象名.getName()
Returns this thread’s name.
4:常见的线程调度模型
抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。java采用的就是抢占式调度模型。
均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式。
java中提供了一些方法是和线程调度有关系
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程优先级
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。Waits for this thread to die.
5:关于多线程开发环境下,数据的安全问题
什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
条件1:多线程并发。
条件2:有共享数据。指向同一个对象
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。用排队执行解决线程安全问题。
这种机制被称为:线程同步机制。
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)
异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。
同步就是排队。
模拟银行账户取款
6:synchronized ()
在java语言中任何一个对象都有一把锁,这把锁就是标记
100个对象,100把锁。1个对象一把锁
1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后t2占有这把锁之后,进入同步代码块执行程序。
这样就达到了线程排队执行。这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。
object 同样是共享对象
Object obj2 = new Object();
synchronized (this)
synchronized (obj)
synchronized (“abc”) // "abc"在字符串常量池当中。
synchronized (null) // 报错:空指针。
synchronized (obj2) // 这样编写就不安全了。因为obj2不是共享对象。
synchronized 出现在方法上,表示整个方法体都要同步,可能无故扩大同步的范围,导致程序执行的效率较低。所以这种方式不常用。
如果共享的对象就是this,并且同步的代码块就是整个方法体,建议使用这种方式。
7:java 中的三大变量
实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中
以上三大变量中:局部变量永远不存在线程问题。因为局部变量不共享,在栈中。
如果使用局部变量:建议使用StringBuilder,因为局部变量不存在线程安全问题。
ArrayList是线程不安全的,Vector是线程安全的;HashMap HashSet是非线程安全的。Hashtable是线程安全的。
8:synchronized面试题
1:关于doOther执行需不需要doSome 执行结束 结论是不需要
/**
* 关于doOther执行需不需要doSome 执行结束 结论是不需要
* doSome 前面没有 synchronized 关键字 不在线程安全问题的考虑范围之内
* 因此不需要等待 doSome 的结束
*/
public class Test {
public static void main(String[] args) {
My my = new My();
Myth myth = new Myth(my);
Myth myth1 = new Myth(my);
myth.setName("t1");
myth1.setName("t2");
myth.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myth1.start();
}
}
class My{
public synchronized void doSome(){
System.out.println("do some begin");
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("do some over");
}
public void doOther(){
System.out.println("do Other begin");
System.out.println("do Other over");
}
}
class Myth extends Thread{
My my;
public Myth(My my) {
this.my = my;
}
@Override
public void run(){
if (Thread.currentThread().getName().equals("t1")){
my.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
my.doOther();
}
}
}
2:对synchronized方法加静态static修饰,称为类锁;类锁不管创建了几个对象,类锁只有一个
package com.Exam;
/**
* 关于doOther执行需不需要doSome 执行结束 结论是不需要
* doSome 前面没有 synchronized 关键字 不在线程安全问题的考虑范围之内
* 因此不需要等待 doSome 的结束
*/
public class Test {
public static void main(String[] args) {
My my = new My();
My my1 = new My();
Myth myth = new Myth(my);
Myth myth1 = new Myth(my1);
myth.setName("t1");
myth1.setName("t2");
myth.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myth1.start();
}
}
class My{
public synchronized static void doSome(){
System.out.println("do some begin");
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("do some over");
}
public synchronized static void doOther(){
System.out.println("do Other begin");
System.out.println("do Other over");
}
}
class Myth extends Thread{
My my;
public Myth(My my) {
this.my = my;
}
@Override
public void run(){
if (Thread.currentThread().getName().equals("t1")){
my.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
my.doOther();
}
}
}
9:死锁
示意图:
不会出现错误。程序一直僵持在那里,这种错误最难调试。
死锁的代码模拟
package com.deadlock;
public class DLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
MyThread1 myThread1 = new MyThread1(o1,o2);
MyThread2 myThread2 = new MyThread2(o1, o2);
myThread1.start();
myThread2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run(){
synchronized (o1){
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread{
Object o1;
Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run(){
synchronized (o2){
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
10:开发中应该如何解决线程安全问题
1:尽量使用局部变量代替实例变量与静态变量
2:如果必须是实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享了
3:不能使用局部变量,对象不能创建多个,那么就只能使用synchronized关键字
11:线程的分类
守护线程(后台线程):最具代表的是垃圾回收线程(守护线程)
一般守护线程是一个死循环,所有用户线程只要结束,守护线程自动结束
系统自动备份,定时器设置为守护线程。
调用setDaemon 将线程设置为守护线程
用户线程
定时器
定时器去的作用:间隔特定的时间,执行特定的程序。
每周进行银行账户的总账造作
每天进行数据的备份操作
在实际开发中每隔多久执行特定的程序,这种需求是常见的
1)可以使用sleep方法,设置睡眠时间,没到这个时间点,执行任务
2)类库中 java.util.Timer
3)高级框架 SpringTask
package com.fangun;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class DingShiqi {
public static void main(String[] args) {
//Timer timer = new Timer();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date parse = simpleDateFormat.parse("2020-10-01 20:40:00");
Timer timer = new Timer();
timer.schedule(new Loger(),parse,1000*5);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
class Loger extends TimerTask{
@Override
public void run() {
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(date);
System.out.println(format+"执行了备份任务");
}
}
FutureTask
实现Callable接口
这种方式可以获取线程的返回值。
之前讲解的那两种方式,run方法返回值都是void
将之前实现线程的需要传入的Runable接口换成Callable
Object wait() notify() 方法(生产者消费者模式)
任何一个javaObject都自带的方法
o.wait() :让正在o对象上活动的线程进入等待状态,无期限的等待下去,知道被唤醒。
会让当前线程(正在o对象上活动的线程)进入等待。
o.notify() 唤醒正在o对象上等待的线程
o.notifyAll() 唤醒正在o对象上等待的所有线程
1、使用wait方法和notify方法实现“生产者和消费者模式”
2、什么是“生产者和消费者模式”?
生产线程负责生产,消费线程负责消费。
生产线程和消费线程要达到均衡。
这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。
3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。
4、wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。
5、wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
6、notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
7、模拟这样一个需求:
仓库我们采用List集合。
List集合中假设只能存储1个元素。
1个元素就表示仓库满了。
如果List集合中元素个数是0,就表示仓库空了。
保证List集合中永远都是最多存储1个元素。
必须做到这种效果:生产1个消费1个。
生产者消费者的代码实现:
package com.ThreadSafe;
import java.util.ArrayList;
import java.util.List;
/**
* 生产需求平衡
*/
public class ShengChanzheXiaoFeizhe {
public static void main(String[] args) {
List list = new ArrayList();
Thread thread = new Thread(new Producer(list));
Thread thread1 = new Thread(new Consumer(list));
thread.setName("生产者线程");
thread1.setName("消费者线程");
thread.start();
thread1.start();
}
}
class Producer implements Runnable{
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
while (true){
synchronized (list){
if (list.size()>0){
try {
list.wait(); //释放之前占用的list锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object o = new Object();
list.add(o);
System.out.println(Thread.currentThread().getName()+"...."+o);
list.notifyAll();
}
}
}
}
class Consumer implements Runnable{
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
while (true){
synchronized (list){
if (list.size()==0){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始消费
Object remove = list.remove(0);
System.out.println(Thread.currentThread().getName()+"..."+remove);
list.notifyAll();
}
}
}
}
利用生产者消费者实现交替输出
package com.ThreadSafe;
public class Test11 {
public static void main(String[] args) {
Number number = new Number(1);
Thread thread = new Thread(new First(number));
Thread thread1 = new Thread(new Second(number));
thread.setName("t1");
thread1.setName("t2");
thread.start();
thread1.start();
}
}
class Number{
private Integer integer;
public Number(Integer integer) {
this.integer = integer;
}
public Integer getInteger() {
return integer;
}
public void setInteger(Integer integer) {
this.integer = integer;
}
}
class First implements Runnable{
private Number number;
public First(Number number) {
this.number = number;
}
@Override
public void run() {
while (true){
synchronized (number){
if (number.getInteger()%2==0){//偶数
try {
number.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+">>>"+number.getInteger());
number.setInteger(number.getInteger()+1);
number.notifyAll();
}
}
}
}
class Second implements Runnable{
private Number number;
public Second(Number number) {
this.number = number;
}
@Override
public void run() {
while (true){
synchronized (number){
if (number.getInteger()%2!=0){
try {
number.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+">>>"+number.getInteger());
number.setInteger(number.getInteger()+1);
number.notifyAll();
}
}
}
}