Java多线程(二)

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

优先级高的不一定先执行,大多数情况是这样的。

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调度了。

优先级的设定建议在start()调度前,setPriority之后紧接start()

守护线程_daemon

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 例子:后台记录操作日志,监控内存,垃圾回收等待等
thread.setDaemon(true);//默认false表示是用户线程

thread即使是永远运行,也会结束,因为是守护线程,JVM不会等待,用户线程结束之后即结束。

线程同步

多个线程操作同一个资源。每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

不安全案例:(ArrayList是线程不安全的)

public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}

可能的原因:1.可能有些线程还在执行时,就输出了size() 2.某个瞬间,往list同一位置添加了两次覆盖了

同步方法及同步块

同步方法: synchronized默认锁的是this这个对象本身(后续反射章节会有涉及)

public synchronized void method(int args){}

同步块:synchronized(obj){}

举例一个不安全银行取钱的例子:

public class TestAccount {
public static void main(String[] args) throws InterruptedException {
Account account = new Account("sum",100);
Drawing you = new Drawing(account,50,"you");
Drawing girlfriend = new Drawing(account,70,"girl");
new Thread(you).start();
new Thread(girlfriend).start();
}
} class Account{
String name;
int Money;
public Account(String name,int money){
this.Money=money;
this.name=name;
}
} class Drawing implements Runnable{
Account account;
int drawingMoney;
int nowMoney=0;
String name; public Drawing(Account account,int drawingMoney, String name) {
this.account = account;
this.drawingMoney = drawingMoney;
this.name = name;
} @Override
public void run() {
if(account.Money-drawingMoney < 0){
System.out.println("there's no money");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.Money = account.Money - drawingMoney;
nowMoney = nowMoney+drawingMoney;
System.out.println("bank remains "+account.Money);
System.out.println(name+" get "+nowMoney);
}
}

结果如下:

银行没有判断出钱不够,这里的sleep放大了事故可能性。

增加同步块:

@Override
public void run() {
synchronized (account){
if(account.Money-drawingMoney < 0){
System.out.println("there's no money");
return ;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.Money = account.Money - drawingMoney;
nowMoney = nowMoney+drawingMoney;
System.out.println(name+" get "+nowMoney);
System.out.println("bank remains "+account.Money);
}
}

共同操作的是account所以obj即为它,同步块里即为原来的方法块,可以锁任何对象(需要锁操作的共同资源对象),这是和synchronized方法不同之处。

用该方法改进上方的不安全ArrayList

public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}

输出即为1000。

提一下并发编程中的安全List(CopyOnWriteArrayList

public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(5000);
System.out.println(list.size());
}

这里的List是线程安全的。

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源释放才能运行。某个同步块同时拥有“两个以上对象的锁”时,就可能发生死锁。

补充:synchronized(obj){}其实可以理解为等待obj释放锁之后执行代码块

产生死锁的四个必要条件:

  • 互斥条件:一个资源每次只能被一个进程使用
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(例:A等B拿的资源,B等A拿的资源)

Lock锁

JDK5.0开始,提供了更强大的线程同步机制,通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.lock接口是控制多个线程对共享资源进行访问的工具。ReentrantLockLock的常用实现类(可重入锁)。

class TestLock2 implements Runnable{

    int ticketNums = 10;
private final ReentrantLock lock = new ReentrantLock(); @Override
public void run() {
while(true){
try {
lock.lock();//加锁
if(ticketNums>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else{
break;
}
} finally { //解锁
lock.unlock();
} }
}
}

仍然是抢票举例,.lock()加锁,.unlock()解锁,以上结果没有冲突情况。

线程协作(生产者消费者问题)

在这个问题中,仅用synchronized是不够的

  • synchronized可阻止并发更新同一个共享资源,实现了同步
  • synchronized不能用来实现不同线程之间的通信

解决方式1:管程法

public class TestPC {   //利用缓冲区解决
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Producer(buffer).start();
new Consumer(buffer).start();
}
} class Producer extends Thread{
Buffer buffer;
public Producer(Buffer buffer){
this.buffer = buffer;
} @Override
public void run() {
for (int i = 1; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
buffer.push(new product(i));
System.out.println("生产了第"+i+"个产品");
}
}
} class Consumer extends Thread{
Buffer buffer;
public Consumer(Buffer buffer){
this.buffer = buffer;
} @Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费了第"+buffer.pop().id+"个产品");
}
}
} class product{
int id; public product(int id) {
this.id = id;
}
} class Buffer{
product[] pro = new product[10]; //缓存区大小
int count=0;
public synchronized void push(product p){
if(count == pro.length){
//通知等待消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
pro[count] = p;
count++; //通知可以消费了
this.notifyAll();
} public synchronized product pop() {
if (count == 0) {
//等待生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
product p2 = pro[count];
this.notifyAll();
return p2;
}
}

此段代码只涉及了新的wait()notifyAll(),其余过程和操作系统相关知识描述一致,可以理一下思路。

解决方式2:信号灯法(利用标志位)建立一个(true,false)标志位进行判断,操作系统相关知识已有,不再详细描述

线程池

背景:经常创建和销毁,使用量特别大

思路:提前创建好多个线程,放在线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用。

好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理

corePoolSize:核心池大小

maximumPoolSize:最大线程数

keepAliveTime:线程没有任务时最多保持多长时间后会终止

简单举例:

public class TestPool {
public static void main(String[] args) {
//创建服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(10); //执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread()); //关闭链接
service.shutdown();
}
} class MyThread implements Runnable{ @Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}

线程池相关知识将在并发编程部分详细介绍。

简述Java多线程(二)的更多相关文章

  1. java 多线程二

    java 多线程一 java 多线程二 java 多线程三 java 多线程四 线程中断: /** * Created by root on 17-9-30. */ public class Test ...

  2. 从零开始学习Java多线程(二)

    前面已经简单介绍进程和线程,为后续学习做铺垫.本文讨论多线程传参,Java多线程异常处理机制. 1. 多线程的参数传递 在传统开发过程中,我们习惯在调用函数时,将所需的参数传入其中,通过函数内部逻辑处 ...

  3. Java多线程(二) —— 深入剖析ThreadLocal

    对Java多线程中的ThreadLocal类还不是很了解,所以在此总结一下. 主要参考了http://www.cnblogs.com/dolphin0520/p/3920407.html 中的文章. ...

  4. 简述Java多线程(一)

    JAVA多线程 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念. 进程:是执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位. 线程是CPU调度和执行的单位. 创 ...

  5. Java多线程(二) 多线程的锁机制

    当两条线程同时访问一个类的时候,可能会带来一些问题.并发线程重入可能会带来内存泄漏.程序不可控等等.不管是线程间的通讯还是线程共享数据都需要使用Java的锁机制控制并发代码产生的问题.本篇总结主要著名 ...

  6. java多线程(二)-Runnable和Thread

    Java在顺序性语言的基础上提供了多线程的支持.Java的线程机制是抢占式的.这表示调度机制会周期的中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片.(与抢占式多线程对应的是 协作式多线 ...

  7. java多线程(二)之实现Runnable接口

    一.java多线程方式2: 实现Runnable接口 好处:a. 可以避免由于java单继承带来的局限性. b. 适合多个相同的程序的代码去处理同一个资源的情况, 把线程与程序的代码, 数据有效分离, ...

  8. java多线程二之线程同步的三种方法

          java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题.java在处理线程同步时,常用方法有: 1.synchronized关键字. 2.Lock显示加锁. 3.信号量Se ...

  9. java多线程(二)

    线程的阻塞状态: 参考java多线程(一)多线程的生命周期图解,多线程的五种状态.     1.1 join(),如果在A线程体里面执行了B线程的join()方法,那么A线程阻塞,直到B线程生命周期结 ...

随机推荐

  1. Nice!JavaScript基础语法知识都在这儿了

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star 转载请注明出处! 链接:https://blog.csdn ...

  2. Python数据结构与算法_搜索插入位置(07)

    给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引.如果目标值不存在于数组中,返回它将会被按顺序插入的位置. 你可以假设数组中无重复元素. 示例 1: 输入: [1,3,5,6], 5输出 ...

  3. Java魔法类:Unsafe应用解析

    本文转载自Java魔法类:Unsafe应用解析 前言 Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别.不安全操作的方法,如直接访问系统内存资源.自主管理内存资源等,这些方法 ...

  4. C++使用libcurl进行http通讯

    借着curl 7.75.0版本更新, 最近又下载下来玩了玩, 在此做个简单记录 1.环境搭建 首先是libcurl动态库, 自己下载源码编译的话如果要使用https协议还要下载OpenSSL和libs ...

  5. Spring Boot移除内嵌Tomcat,使用非web方式启动

    前言:当我们使用Spring Boot编写了一个批处理应用程序,该程序只是用于后台跑批数据,此时不需要内嵌的tomcat,简化启动方式使用非web方式启动项目,步骤如下: 1.在pom.xml文件中去 ...

  6. Numpy初体验

    目录 Numpy 一.简介 1.安装 2.特殊的导包 二.ndarray-多维数组对象 1.创建ndarray数组 1.1 array 1.2 arange 1.3 linspace 1.4 zero ...

  7. docker封装Spring Cloud(单机版)

    一.概述 微服务统一在一个git项目里面,项目的大致结构如下: ./ ├── auth-server │ ├── pom.xml │ └── src ├── common │ ├── pom.xml ...

  8. 原生JS快速实现拖放

    原生 JS 快速实现拖放 拖放是很常见的一种交互效果,很多时候我们都会借助于第三方的控件来实现,其实用原生 js 实现起来也非常的方便.接下来我们就用原生 js 和 css 快速实现拖放效果 html ...

  9. zabbix 面板graph图上没有数据显示

    1. 问题: zabbix_server日志出现大量如下错误: query failed: [1526] Table has no partition for value 1507509984 2. ...

  10. 冒泡算法及其优化(java)

    冒泡算法的规则: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放待排序序列的起始位置(或末尾位置),直到全部待排序的数据元素排完 动态图: 对应代码 static void swap(i ...