多线程的实现及其安全问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程的实现及其安全问题相关的知识,希望对你有一定的参考价值。
一、进程和线程概述
1、进程:进程是一个具有独立功能的程序关于某个数据集合的一次运行活动,简单来说开启一个程序就开启了一个进程;
如果开启多个进程,它们之间是由于CPU的时间片在相互的切换;
2、线程:开启一个进程的一个任务,对于多线程:每一个线程都在争夺CPU的执行权(CPU的执行权具有随机性);
如果一个程序的执行路径有多条,那么该线程是多线程;反之,就单线程线程;线程是依赖于进程存在的!
3、Jvm是多线程 —— 至少开启了两条线程
main方法 主线程
gc() 垃圾回收线程
二、多线程的实现方式
1、继承Thread类
1)自定义一个类,该类继承Thread类
2)重写的run()方法
public class MyThread extends Thread{
public void run(){
耗时操作;
}
}
3)在主线程中创建该类对象
MyThread mt = new MyThread(); //创建几个对象,就有几个线程,去执行run()方法中的耗时操作
4)start()方法,启动线程
mt.start();
2、实现Runnable接口
1)自定义一个类,该类实现Runnable接口 //数据共享
2)重写的run()方法
public class MyRunnable implements Runnable{
public void run(){
耗时操作
}
}
3)在主线程中创建该类的对象
MyRunnable mr = new MyRunnable();
4)创建Thread类对象,将第三步的对象作为参数进行传递
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr,"名称"); //可以设置线程名称
5)start()方法,启动线程
t1.start();
t2.start();
3、使用线程池(主要用于计算)
1)自定义一个类,实现Callable接口
public class MyCallable implements Callable{}
2)实现里面的call方法
相当于run()方法,里面放一些耗时操作;
3)主线程中创建线程对象
ExecutorService threadPool = Executors.newFixedThreadPool(2) ; 参数表示线程的个数
4)用线程池对象提交任务
threadPool.submit(new MyCallable());
threadPool.submit(new MyCallable());
5)提交后结束线程池
threadPool.shutdown();
三、Thread的常用方法(Thread实现Runnable接口)
public static Thread currentThread()返回对当前正在执行的线程对象的引用
public final String getName()返回该线程的名称
public final void setName(String name)改变线程名称
public final void setDaemon(boolean on)当参数为true时,设置该线程为守护线程,当正在运行的线程都是守护线程时Java虚拟机退出
//该方法必须在启动线程前调用
public final void join()throws InterruptedException等待该线程终止 //等待该线程执行完毕,其他线程才能执行
public static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
public final void stop()强制停止执行 //已过时
public void interrupt()中断线程 //中断当前线程的这种状态(打破了一种状态)
public static void yield()暂停当前正在执行的线程对象,并执行其他线程
//线程的执行具有随机性,下次有可能又是它抢占到了CPU执行权,还是它接着执行
四、多线程的安全问题
egg:共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
public class SellTicket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket --;
}else{
break;
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
1、问题:
1)出现了同一张票被卖了多次
由于CPU的执行具有原子性(最简单的操作)操作
2)出现了0票和负票
由于cpu的执行权具有随机性和延迟性导致
2、线程安全的检测条件
1)看我们当前的环境是否属于多线程程序
2)当前的多线程程序中是否有共享数据
3)是否多条语句对共性数据进行操作
3、多线程的同步机制
为了解决上述安全问题java提供了一个同步机制 —— 关键词sychronized
1)同步代码块的使用:
sychronized(任意对象){
多条语句对共享数据进行操作的代码;
}
2)注意事项:
A:所有线程只能使用同一个对象(将任意对象当作一个锁:使用的是同一把锁),所以不能使用匿名对象当做参数
B:注意同步代码块所包的代码不能将循环包进去,否则就不是多线程了
3)同步方法:
public static synchronized void 方法名(){
多条语句对共享数据进行操作的代码;
} //同步方法使用锁对象是this
静态的同步方法的锁对象是:类名.class(java中的反射机制)
4)Lock接口:
实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
创建具体的锁对象
private Lock lock = new ReentrantLock(); //Lock接口不能实例化,提供了子实现类:ReentrantLock
public void lock()获取锁
多条语句对共享数据进行操作的代码;
public void unlock():释放锁
使用:一般使用try-finally将整个内容包起来,在finally中释放锁
4、修改后代码:
public class SellTicket implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while(true){
sellTicket();
if(ticket <= 0){
break;
}
}
}
public static synchronized void sellTicket(){
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket --;
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
5、和线程安全相关的类
StringBuffer sb = new StringBuffer();
Vector<T> v = new Vector<T>() ;
Hashtable<K, V> hs = new Hashtable<K,V>() ;
public static <T> List<T> synchronizedList(List<T> list)返回指定列表支持的同步(线程安全的)列表
List<T> list = Collections.synchronizedList(new ArrayList<T>());
五、死锁的问题
1、线程安全的弊端:
1)执行效率低
2)容易产生死锁
两个或两个以上的线程,抢占CPU的执行权,然后出现了互相等待的情况;
2、生产者消费者模式:
分别进行产生数据和消费数据两条线程,针对同一资源进行操作
egg:
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();
}
}
public class Student {
private String name;
private int age;
public Student() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class SetThread implements Runnable{
private Student s;
public SetThread(Student s) {
super();
this.s = s;
}
@Override
public void run() {
int i = 0;
while(true){
if(i % 2 == 0){
s.setName("张三");
s.setAge(30);
}else{
s.setName("李四");
s.setAge(40);
}
i ++;
}
}
}
/*
* 理想状态:
* 张三=30
* 李四=40
* 张三=30
* 李四=40
* 循环...
*/
public class GetThread implements Runnable{
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while(true){
System.out.println(s.getName() + "=" + s.getAge());
}
}
}
3、问题:
1)出现了 张三=40 线程随机性导致
解决方法:给代码加上同步代码块
2)并没有和理想状态一样,张三、李四依次出现,而是同一个数据被打印了多次
解决方法:等待唤醒机制
注意:同步锁,不同的线程之间使用的是同一把锁对象!
Object类中有关多线程的方法:
public final void wait()throws InterruptedException当前线程等待
public final void notify()唤醒在此对象监视器上等待的单个线程
public final void notifyAll()唤醒在此对象监视器上等待的所有线程
//这些方法一般是使用的锁对象进行调用,锁对象可以是任意对象,所以这几个有关多线程的方法在Object类中
4、修改后代码:
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student();
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
new Thread(st).start();
new Thread(gt).start();
}
}
public class Student {
private String name;
private int age;
private boolean falg;
public Student() {
super();
}
public synchronized void set(String name,int age){
if(falg == true){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.age = age;
falg = true;
this.notify();
}
public synchronized void get(){
if(falg == false){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.name + "=" + this.age);
falg = false;
this.notify();
}
}
public class SetThread implements Runnable{
private Student s;
public SetThread(Student s) {
super();
this.s = s;
}
@Override
public void run() {
int i = 0;
while(true){
if(i % 2 == 0){
s.set("张三", 30);
}else{
s.set("李四", 40);
}
i++;
}
}
}
public class GetThread implements Runnable{
private Student s;
public GetThread(Student s) {
super();
this.s = s;
}
@Override
public void run() {
while(true){
s.get();
}
};
}
六、定时机制
1、定时器:可以进行任务的重复操作
可安排任务定时执行一次,或者定期重复执行
2、Timer类
1)Timer的使用
构造方法:public Timer()创建一个新计时器
方法: public void schedule(TimerTask task, Date time)安排在指定的时间执行指定的任务
参数1:task - 所要安排的任务
参数2:time - 执行该任务的时间毫秒值
public void schedule(TimerTask task, Date firstTime,long period)每隔多少毫秒进行重复性的任务操作
public boolean cancel()取消此计时器任务
2)TimerTask类
需要自定义类去继承,并重写其run()方法;作为所要安排的任务
3)执行一个定时器,3秒之后爆炸,并且每隔2秒继续执行
public class TimerDemo{
public static void main(String[] args){
Timer t = new Timer();
t.schedule(new MyTask(), 3000, 2000);
}
}
class MyTask extends TimerTask{
@Override
public void run() {
System.out.println("bom,爆炸了");
}
}
以上是关于多线程的实现及其安全问题的主要内容,如果未能解决你的问题,请参考以下文章
27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)
27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)
多线程单连接中的 PDO::lastInsertId() 是不是安全?
java 22 - 12 多线程之解决线程安全问题的实现方式1
JAVA笔记(19)--- 线程概述;如何实现多线程并发;线程生命周期;Thread常用方法;终止线程的三种方式;线程安全问题;synchronized 实现同步线程模型;