-
多线程常用方法详解及案例分析
多线程编程
多线程就是把操作系统中的这种并发执行机制原理运用在一个程序中,把一个程序划分为若干个子任务,多个子任务并发执行,每一个任务就是一个线程。 这就是多线程程序 。
1、使用线程可以把占据时间长的 程序 中的 任务 放到 后台 去处理 。
2、用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度 。
3、程序的运行速度可能加快 。
4、在一些等待的 任务 实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。 在这种 情况 下可以释放一些珍贵的资源如 内存 占用等 。
5、 多线程技术 在IOS软件开发中也有举足轻重的作用 。
线程常用
synchronized //同步锁
volatile //可见
CountDownLatch //计数器
ReenTrantLock //重入锁(公平锁)
TimeUnit //配合时间 例(TimeUnit.SECONDS.sleep(1)睡眠一秒)
Semaphore
lockInterruptibly //中断锁
lock //上锁
unlock //解锁
tryLock //判断有无锁 返回boolean
wait //线程等待
sleep //线程睡眠
notify //线程唤醒
多线程实现的方法
一共有三种,常用的有继承Thread类、实现Runnable接口
还有一种实现Callable接口下面主要介绍常用的两种
因为继承只能单继承而实现可以多实现因此推荐使用实现接口
继承Thread类
1.新建类继承Thread类
public class Test1 {
public static void main(String[] args) {
T1 t = new T1();
t.start();
}
}
class T1 extends Thread {
@Override
public void run() {
}
}
2.匿名内部类
public class Test1 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
}
}
3.lambda表达式
public class Test1 {
public static void main(String[] args) {
new Thread(() -> {
}).start();
}
}
实现Runnable接口
1.新建类实现接口
public class Test1 {
public static void main(String[] args) {
T1 t =new T1();
Thread tt =new Thread(t);
tt.start();
}
}
class T1 implements Runnable{
@Override
public void run() {
}
}
2.匿名内部类
public class Test1 {
public static void main(String[] args) {
var t = new Runnable() {
@Override
public void run() {
}
};
Thread tt =new Thread(t);
tt.start();
}
}
多线程常用方法
下面是多线程状态一定要记住!!!!
设置和获取线程的优先级
优先级的范围(1-10)默认是5
//Thread.MAX_PRIORITY 10
//Thread.MIN_PRIORITY 1
//Thread.NORM_PRIORITY 5
Thread.currentThread().setPriority(Thread.MAX_PRIORITY)//设置该线程优先级
Thread.currentThread().getPriority()//获取该线程优先级
设置和获取线程的名字
Thread.currentThread().setName("t1")//设置该线程姓名
Thread.currentThread().getName()//获取该线程姓名
线程睡眠
sleep不会释放同步锁后面会详解先介绍使用方法
Thread.sleep(1000)//让该线程睡眠1秒TimeUnit.SECONDS.sleep(1)//让该线程睡眠一秒
线程等待
wait会释放同步锁进行等待后面会配合介绍
wait()//
多线程工具类
Timer TimerTask
定时器
public class ThreadTimer { public static void main(String[] args) throws ParseException { Timer timer = new Timer(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse("2021-8-24 20:14:30");//设置指定的时间 timer.schedule(new TimerTask() { @Override public void run() { System.out.printf("%1$tF %1$tT %n", System.currentTimeMillis()); } }, d, 3000);//d是开始执行的时间到d才会执行 3000是开始执行后每3秒执行一次 Timer t2 = new Timer(); t2.schedule(new TimerTask() { @Override public void run() { timer.cancel();//对第一个定时器的撤销 t2.cancel();//t2定时器撤销 } }, 20 * 1000);//20秒后开始执行 }}
TimeUnit
java.util.concurrent包下面的一个类表示给定单元粒度的时间段
CountDownLatch
public class Thread9 { public static void main(String[] args) { new Thread(Test::add, "t1").start();//这里第一个参数是lambda表达式表示使用add方法第二个参数是设置该线程名字 new Thread(() -> { while (!Test.count()) ; System.out.println("已经有五个了"); }, "t2").start(); }}class Test { static volatile List<String> list = new ArrayList<>(); private static CountDownLatch latch = new CountDownLatch(1);//这里的参数是表示要打开几次才能打开 public static void add() { String tn = Thread.currentThread().getName();//获取线程名称 System.out.println(tn + "线程启动"); while (true) { try { TimeUnit.SECONDS.sleep(1);//睡眠一秒 } catch (InterruptedException e) { e.printStackTrace(); } list.add("item:" + (list.size() + 1));每次循环在list加一个元素 System.out.printf("%s = %s %n", list.size(), list.get(list.size() - 1)); //当list有5个元素时打开一次 if (list.size() == 5) { latch.countDown(); } } } //用于计数判断个数 public static boolean count() { boolean f = false; try { latch.await();//线程等待当门闩被打开时将才会像下执行 f = true; } catch (InterruptedException e) { e.printStackTrace(); } return f; }}
synchronized
同步锁(独占锁)
ReentrantLock常常对比着synchronized来分析,我们先对比着来看然后再一点一点分析。
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
ReentrantLock好像比synchronized关键字没好太多,我们再去看看synchronized所没有的,一个最主要的就是ReentrantLock还可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。
同步方法格式(锁的对象是this)
修饰符 synchronized 返回值类型 方法名(方法参数) {方法体;}
同步静态方法格式(锁的对象是.class)
修饰符 static synchronized 返回值类型 方法名(方法参数) {方法体;}
同步代码块(锁的对象是由自己定)
synchronized(对象){方法体}
下面用一个购票来使用同步锁
若不加锁的话线程是不安全的则购票就会出现脏读和错读
例票会出现同样的和票数会出现负数
public class Thread6 { public static void main(String[] args) throws InterruptedException { Thicket t = new Thicket(); Thread t1 = new Thread(t, "t1"); Thread t2 = new Thread(t, "t2"); Thread t3 = new Thread(t, "t3"); t1.start(); t2.start(); t3.start(); }}class Thicket implements Runnable { int i = 100; @Override public void run() { while (true) { synchronized (this) { if (i == 0) { break; } System.out.println(Thread.currentThread().getName() + "拿到了第" + i + "张票"); i--; } } }}
ReentrantLock
重入锁(公平锁):每一个线程都会执行一遍后在执行
public class Test1 { Lock lock = new ReentrantLock(true);//设置为true公平锁,效率低但公平 void m() { for (int i = 0; i <= 20; i++) { lock.lock();//加锁 System.out.println(Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } lock.unlock();//解锁 } } public static void main(String[] args) { var t = new Test1(); new Thread(t::m, "T1").start(); new Thread(t::m, "T2").start(); new Thread(t::m, "T3").start(); new Thread(t::m, "T4").start(); }}
/** * @author sulishijian * @date 2021/8/26-14:14 * @since 16 */public class Test1 { static Lock lock = new ReentrantLock(); public static void main(String[] args) { var t = new Thread(Test1::test); t.start(); t.interrupt();//打破锁 } static void test() { try { lock.lockInterruptibly(); for (int i = 0; i < 10; i++) { System.out.println(i); } } catch (InterruptedException e) { System.out.println("......"); } finally { if (lock.tryLock()) { lock.unlock(); System.out.println("线程二被打断"); } } }}
wait sleep
1 sleep()实现线程阻塞的方法,我们称之为“线程睡眠”,方式是超时等待
2 wait()方法实现线程阻塞的方法,我们称之为“线程等待”和sleep()方法一样,通过传入“睡眠时间”作为参数,时间到了就“醒了”;
不传入时间,进行一次“无限期的等待”,只用通过notify()方法来“唤醒”。
3 sleep()释放CPU执行权,但不释放同步锁;
4 wait()释放CPU执行权,也释放同步锁,使得其他线程可以使用同步控制块或者方法。
两个案例第一个是管程法第二个是信号灯法来深入去了解wait
package demo1;/** * @author sulishijian * @date 2021/8/20-19:24 * @since 16 */public class Thread4 { public static void main(String[] args) { Panel panel = new Panel(); new Thread(() -> { for (int i = 0; i < 100; i++) { System.out.printf("生产了第%d只鸡%n", i); panel.push(new Chicken(i)); } }).start(); new Thread(() -> { for (int i = 0; i < 100; i++) { System.out.printf("消费了第%d只鸡%n", panel.pop().id); } }).start(); }}//定义一个共同的类class Chicken { int id; public Chicken(int id) { this.id = id; }}//容器类class Panel { volatile Chicken[] chickens = new Chicken[10];//定义一个对象数组用来存储数据volatile用于让两个线程都可同时看到数组数据 int count = 0; public synchronized void push(Chicken chicken) { //如果个数有十个就进行等待消费 if (count == chickens.length) { try { this.wait();//释放锁进行等待唤醒 } catch (InterruptedException e) { e.printStackTrace(); } } chickens[count] = chicken;//将生产的东西放入数组中 count++;//计数器加一 this.notify();//如果没有十个则去唤醒消费者去消费 } public synchronized Chicken pop() { //如果个数为0则进行等待生产 if (count == 0) { try { this.wait();//进行等待 } catch (InterruptedException e) { e.printStackTrace(); } } count--;//计数器减一 Chicken chicken = chickens[count];//将东西取出 this.notify();//唤醒生产者生产 return chicken; }}
package demo1;/** * @author sulishijian * @date 2021/8/26-20:17 * @since 16 */public class Thread14 { public static void main(String[] args) { TV tv = new TV(); new Thread(() -> { for (int i = 0; i < 20; i++) { if (i % 2 == 0) { tv.play("航海王"); } else { tv.play("斗罗大陆"); } } }).start(); new Thread(() -> { for (int i = 0; i < 20; i++) { tv.watch(); } }).start(); }}class TV { //若没有节目则flag为false开始表演 若有节目则为true开始观看 volatile boolean flag = false; String voice; public synchronized void play(String voice) { //如果为true则有节目 进行等待观众观看 if (flag) { try { this.wait();//释放锁进行等待唤醒 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("表演了:" + voice); this.notifyAll();//此时为false唤醒观众进行观看 this.voice = voice;//表演的节目 this.flag = !this.flag;//false变成true } public synchronized void watch() { //如果为false则为没有节目 进行等待演员表演 if (!flag) { try { this.wait();//释放锁进行等待唤醒 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观看了:" + voice); this.notifyAll();//唤醒演员进行表演 this.flag = !this.flag;//将true变成false }}
join
ThreadTest t1=new ThreadTest("A");
ThreadTest t2=new ThreadTest("B");
t1.start();
t1.join();
t2.start();
先执行t1线程,再执行t2线程。ThreadTest t1=new ThreadTest("A");
ThreadTest t2=new ThreadTest("B");
ThreadTest t3=new ThreadTest("C");
t1.start();
t2.start();
t1.join();
t3.start();
t1线程和t2线程交替执行,当t1线程执行完后开始t3线程,执行过程中和t2没有关系多次实验可以看出,主线程在t1.join()方法处停止,并需要等待A线程执行完毕后才会执行t3.start(),然而,并不影响B线程的执行。因此,可以得出结论,t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。
PS:join源码中,只会调用wait方法,并没有在结束时调用notify,这是因为线程在die的时候会自动调用自身的notifyAll方法,来释放所有的资源和锁。
实现三个线程,运行输出 A1 B2 C3 A4 B5 C6 …..
/** * @author sulishijian * @date 2021/8/26-14:14 * @since 16 */public class Test1 { AtomicInteger num = new AtomicInteger(0); synchronized void work() { try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } String n = Thread.currentThread().getName(); System.out.printf("%s%d ", n, num.incrementAndGet()); if ("C".equals(n)) { System.out.println(); } } public static void main(String[] args) throws InterruptedException { var t = new Test1(); while (true) { var t1 = new Thread(t::work, "A"); t1.start(); t1.join(); var t2 = new Thread(t::work, "B"); t2.start(); t2.join(); var t3 = new Thread(t::work, "C"); t3.start(); t3.join(); } }}
interrupt
/** * @author sulishijian * @date 2021/8/26-16:44 * @since 16 */public class Thread12 { static boolean flag = false; public static void main(String[] args) { var t = new Thread(Thread12::work); t.start(); try { TimeUnit.SECONDS.sleep(5); t.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void work() { Thread t = Thread.currentThread(); String tt = t.getName(); System.out.printf("%s线程启动了%n", tt); System.out.println(t.isInterrupted()); while (!t.isInterrupted()) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { break; } System.out.println("......."); } System.out.println(t.isInterrupted()); System.out.printf("%s线程结束了%n", tt); }}
多线程实例
线程安全
出现安全问题的条件
是多线程环境有共享数据有多条语句操作共享数据
解决方法
线程同步(排队机制)
有线程安全问题的变量
//局部变量永远都不会存在线程安全问题(局部变量在栈中永远不会共享)实例变量静态变量
死锁
例如有A线程和B线程 第一个线程在A锁里面写B锁 第二个线程在B锁里面写A锁
A拿着A锁在去拿B锁 B拿着B锁在去拿A锁两个线程都占着锁想要对方的锁就会一直僵持然后形成死锁
建议在锁里面睡眠一秒 否则有可能A运行过快以下拿完全部锁
/** * @author sulishijian * @date 2021/8/26-14:14 * @since 16 */public class Test1 { static Object A = new Object(); static Object B = new Object(); public static void TA() { synchronized (A) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A"); synchronized (B) { System.out.println("B"); } } } public static void TB() { synchronized (B) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B"); synchronized (A) { System.out.println("A"); } } } public static void main(String[] args) { new Thread(Test1::TA).start(); new Thread(Test1::TB).start(); }}
求123456789 之间放+-和100的表达式,如果一个线程求出结果,立即告诉其它停止
/** * @author sulishijian * @date 2021/8/25-15:47 * @since 16 */public class Thread10 { static volatile Set<String> set = new HashSet<>(); static void op() { System.out.println(Thread.currentThread().getName() + "启动中"); String[] o = new String[]{"", "+", "-"}; Random rand = new Random(); Pattern p = Pattern.compile("-?\\d+"); while (set.size() != 10) { StringBuffer sbf = new StringBuffer("1"); for (int i = 2; i <= 9; i++) { sbf.append(String.format("%s%d", o[rand.nextInt(o.length)], i)); } String s = sbf.toString(); Matcher m = p.matcher(s); List<Integer> list = new ArrayList<>(); while (m.find()) { list.add(Integer.parseInt(m.group())); } int sum = list.stream().reduce(0, Integer::sum); if (sum == 100 && !set.contains(s)) { set.add(s); System.out.println(Thread.currentThread().getName() + ":" + s); } } } public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(Thread10::op, "i" + i).start(); } }}
加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
/** * @author sulishijian * @date 2021/8/25-9:46 * @since 16 */public class Thread9 { public static void main
出处:https://www.cnblogs.com/sulishijian/p/15200402.html