java并发编程 线程基础

1. java中的多线程

java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动

public class OnlyMain {
public static void main(String[] args) {
//虚拟机线程管理接口
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo info : threadInfos) {
System.out.printf("[%s] %s\n",info.getThreadId(),info.getThreadName());
}
}
}
////////////////////////////////控制台输出
[6] Monitor Ctrl-Break
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main
  • Monitor Ctrl-Break:IntelliJ IDEA执行用户代码的时候,实际是通过反射方式去调用,而与此同时会创建一个Monitor Ctrl-Break 用于监控目的。
  • Attach Listener:该线程是负责接收到外部的命令,执行该命令,并且把结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,得到启动。
  • signal dispather: 前面我们提到第一个Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。
  • Finalizer: JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收。
  • Reference Handler :它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。
  • main:主线程,用于执行我们编写的java程序的main方法。

2. 启动多线程的方式

  1. 继承Thread类

  2. 实现runnable接口

  3. 实现callable接口

private static class ThreadClass extends Thread{
@Override
public void run() {
System.out.println("this is threadClass");;
}
} private static class RunnableClass implements Runnable{
@Override
public void run() {
System.out.println("this is runnableClass");;
}
} private static class CallableClass implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("this is callableClass");;
return "callableClass";
}
} public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadClass threadClass = new ThreadClass();
RunnableClass runnableClass = new RunnableClass();
CallableClass callableClass = new CallableClass();
// extends Thread start
threadClass.start();
// implements runnable start
new Thread(runnableClass).start();
// implements callable start
FutureTask<String> futureTask = new FutureTask<>(callableClass);
new Thread(futureTask).start();
System.out.println(futureTask.get());;
}
////////////////////////////////控制台输出
this is threadClass
this is runnableClass
this is callableClass
callableClass

3. 线程的停止

  1. 自然执行完或抛出异常

  2. 调用stop(),resume(),suspend()方法,但这些方法线程不会释放资源,会造成死锁;所以已经被jdk废弃

  3. interrupt(),isInterrupted(),static interrupted()

    interrupt() 中断一个线程,不是强制停止,通过协作的方式进行,将中断标志位置为true

    isInterrupted() 判定当前线程是否处于中断状态

    static interrupted() 判定当前线程是否处于中断状态,将中断标志位置为false

    private static class ThreadClass extends Thread {
    @Override
    public void run() {
    String threadName = Thread.currentThread().getName();
    while (!isInterrupted()) {
    System.out.println(threadName +" is run!");
    }
    System.out.println(threadName + " flag is " + isInterrupted());
    }
    } private static class RunnableClass implements Runnable {
    @Override
    public void run() {
    String threadName = Thread.currentThread().getName();
    while (!Thread.currentThread().isInterrupted()) {
    System.out.println(threadName +" is run!");
    }
    System.out.println(threadName + " flag is " + Thread.currentThread().isInterrupted());
    }
    } public static void main(String[] args) throws InterruptedException {
    ThreadClass threadClass = new ThreadClass();
    threadClass.start();
    Thread.sleep(10);
    threadClass.interrupt(); RunnableClass runnableClass = new RunnableClass();
    Thread runnableThread = new Thread(runnableClass,"runnableClass");
    runnableThread.start();
    Thread.sleep(10);
    runnableThread.interrupt();
    }

    warning:如果线程中有InterruptedException异常的话,这是因为InterruptedException会重置异常标志位为false,会对异常中断位有影响,下面程序重复运行几次就会产生这样的异常情况。解决的方法就是在catch块中添加interrupt()

    private static class ThreadClass extends Thread {
    @Override
    public void run() {
    String threadName = Thread.currentThread().getName();
    while (!isInterrupted()) {
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    System.out.println(threadName + " flag1 is " + isInterrupted());
    // interrupt();
    e.printStackTrace();
    }
    System.out.println(threadName + " flag2 is " + isInterrupted());
    }
    System.out.println(threadName + " flag3 is " + isInterrupted());
    }
    } public static void main(String[] args) throws InterruptedException {
    ThreadClass threadClass = new ThreadClass();
    threadClass.start();
    Thread.sleep(200);
    threadClass.interrupt();
    } //////////////////////////异常结果
    Thread-0 flag2 is false
    Thread-0 flag2 is false
    Thread-0 flag1 is false
    java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.thread.demo.InterruptExceptionThread$ThreadClass.run(InterruptExceptionThread.java:16)
    Thread-0 flag2 is false
    Thread-0 flag2 is false //////////////////////////正常结果
    Thread-0 flag2 is false
    Thread-0 flag2 is false
    Thread-0 flag3 is true

4. 守护线程

当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

在java中可以通过setDaemon(true)的方式将一个线程设置为守护线程

守护线程值得注意的地方:

  • thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。不能把正在运行的常规线程设置为守护线程。
  • 在Daemon线程中产生的新线程也是Daemon的。
  • 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。
  • 守护线程中包含try...catch...finally的时候,finally中的内容不一定能实现。
//读写操作
private static class WriteFileRunnable implements Runnable {
@Override
public void run() {
try {
File f = new File("daemon.txt");
FileOutputStream os = new FileOutputStream(f, true);
os.write("daemon".getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
WriteFileRunnable writeFileRunnable = new WriteFileRunnable();
Thread writeThread = new Thread(writeFileRunnable);
writeThread.setDaemon(true);
writeThread.start();
}
//////////////////////////结果
//文件daemon.txt中没有"daemon"字符串。 //包含try...catch...finally
private static class ThreadClass extends Thread {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
while (true) {
System.out.println(threadName + " is run!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("....................finally");
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadClass threadClass = new ThreadClass();
threadClass.setDaemon(true);
threadClass.start();
Thread.sleep(10);
}
//////////////////////////结果
Thread-0 is run!
Thread-0 is run!
Thread-0 is run!
Thread-0 is run! Process finished with exit code 0

5. synchronized内置锁

在并发编程中存在线程安全问题,主要原因有:存在共享数据多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性)。

synchronized分为类锁对象锁,synchronized添加在方法中和使用synchronized(this)都是使用了对象锁,如果方法定义成静态的,再添加synchronized,此时锁为类锁。类锁和对象锁可以并行运行,不同对象的对象锁也是可以并行运行的。

  1. synchronized作用于实例方法
public class SynchronizedDemo {
private static class InstanceSyncMethod implements Runnable {
static int tag = 1; //共享资源 public synchronized void increase() {
tag++;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
increase();
}
}
} public static void main(String[] args) throws InterruptedException {
InstanceSyncMethod instance = new InstanceSyncMethod();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(InstanceSyncMethod.tag);
}
}
//////////////////////////输出结果
2001

synchronized用于实例对象的时候存在一个问题,当创建多个实例的时候,虽然方法上使用了synchronized,但是因为存在多个不同的实例对象锁,因此t都会进入各自的对象锁,也就是说多个线程使用的是不同的锁,因此线程安全是无法保证的。

//new新实例
Thread t1=new Thread(new InstanceSyncMethod());
//new新实例
Thread t2=new Thread(new InstanceSyncMethod());
t1.start();
t2.start();
//join含义:当前线程A等待thread线程终止之后才能从thread.join()返回
t1.join();
t2.join();
System.out.println(InstanceSyncMethod.tag);
////////////////////////////这个时候输出的结果就不确定是多少

解决这种困境的的方式是将synchronized作用于静态的方法,这样的话,对象锁就当前类对象,由于无论创建多少个实例对象,但对于的类对象拥有只有一个,所有在这样的情况下对象锁就是唯一的。

  1. synchronized作用于静态方法

synchronized作用于静态方法时,其锁就是当前类的class对象锁。调用一个实例对象的非static synchronized方法,和静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

private static class InstanceSyncMethod implements Runnable {
static int tag = 1; //共享资源 public static synchronized void increase() {
tag++;
} @Override
public void run() {
for (int i = 0; i < 1000; i++) {
increase();
}
}
} public static void main(String[] args) throws InterruptedException {
//new新实例
Thread t1 = new Thread(new InstanceSyncMethod());
//new新实例
Thread t2 = new Thread(new InstanceSyncMethod());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(InstanceSyncMethod.tag);
}
//////////////////////////输出结果
2001
  1. synchronized同步代码块

在某些情况下,方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,就不建议直接对整个方法进行同步操作,此时可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作。

private static class InstanceSyncMethod implements Runnable {
static int tag = 1; //共享资源 @Override
public void run() {
synchronized (this){
for (int i = 0; i < 1000; i++) {
tag++;
}
}
}
} public static void main(String[] args) throws InterruptedException {
//new新实例
Thread t1 = new Thread(new InstanceSyncMethod());
//new新实例
Thread t2 = new Thread(new InstanceSyncMethod());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(InstanceSyncMethod.tag);
}
//////////////////////////输出结果
2001 //////////////////////////类似的方法块
//this,当前实例对象锁
synchronized(this){
...
} //class对象锁
synchronized(XXX.class){
...
}

6. volatile

volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。

volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性。在Java内存模型中,有main memory,每个线程也有自己的memory。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。

一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。

也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。

下面程序就是验证volatile不能保证线程执行的有序性。

private static class VolatileClass implements Runnable {
private volatile int a = 1; @Override
public void run() {
String threadName = Thread.currentThread().getName();
a = a + 1;
System.out.println(threadName +":=" + a);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
a = a + 1;
System.out.println(threadName +":=" + a);
}
} public static void main(String[] args) {
VolatileClass volatileClass = new VolatileClass(); Thread t1 = new Thread(volatileClass);
Thread t2 = new Thread(volatileClass);
Thread t3 = new Thread(volatileClass);
Thread t4 = new Thread(volatileClass);
t1.start();
t2.start();
t3.start();
t4.start();
}
//////////////////////////输出结果
Thread-0:=2
Thread-3:=5
Thread-2:=4
Thread-1:=3
Thread-3:=7
Thread-1:=7
Thread-0:=7
Thread-2:=7

7. ThreadLocal

threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。

private static ThreadLocal<Integer> localNum = new ThreadLocal<Integer>() {
@Override
public Integer initialValue() {
return 1;
}
};
private static class ThreadLoaclClass implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
localNum.set(localNum.get() + 1);
System.out.println(threadName +":=" + localNum.get());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
localNum.set(localNum.get() + 1);
System.out.println(threadName +":=" + localNum.get());
}
}
public static void main(String[] args) throws InterruptedException {
ThreadLoaclClass loaclClass = new ThreadLoaclClass(); Thread t1 = new Thread(loaclClass);
Thread t2 = new Thread(loaclClass);
Thread t3 = new Thread(loaclClass);
Thread t4 = new Thread(loaclClass);
t1.start();
t2.start();
t3.start();
t4.start();
}
//////////////////////////输出结果
Thread-0:=2
Thread-3:=2
Thread-1:=2
Thread-2:=2
Thread-1:=3
Thread-2:=3
Thread-0:=3
Thread-3:=3

每个线程所产生的序号虽然都共享同一个实例,但它们并没有发生相互干扰的情况,而是各自产生独立的数字,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

ThreadLocal和Synchronized都能解决多线程中相同变量的访问冲突问题,不同的是

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

java并发编程 线程基础的更多相关文章

  1. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  2. java并发编程 | 线程详解

    个人网站:https://chenmingyu.top/concurrent-thread/ 进程与线程 进程:操作系统在运行一个程序的时候就会为其创建一个进程(比如一个java程序),进程是资源分配 ...

  3. Java并发编程:线程间通信wait、notify

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  4. Java并发编程:线程和进程的创建(转)

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  5. Java并发编程——线程池的使用

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  6. Java并发编程——线程池

    本文的目录大纲: 一.Java中的ThreadPoolExecutor类 二.深入剖析线程池实现原理 三.使用示例 四.如何合理配置线程池的大小 一.Java中的ThreadPoolExecutor类 ...

  7. Java并发编程之美之并发编程线程基础

    什么是线程 进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程至少有一个线程,进程的多个线程共享进程的资源. java启动main函数其实就 ...

  8. Java 并发编程实践基础 读书笔记: 第二章 构建线程安全应用程序

    1,什么是线程安全性? 简单概括就是一个类在多线程情况下能安全调用就是线程安全 2,Servlet  的线程安全性  默认是非线程安全的,写servlet代码的时候需要注意线程安全,注意同步 3,vo ...

  9. Java并发编程-线程可见性&线程封闭&指令重排序

    一.指令重排序 例子如下: public class Visibility1 { public static boolean ready; public static int number; } pu ...

随机推荐

  1. 使用fastjson的parseObject方法将json字符串转换成Map 或者List

    fastjson 转换成map HashMap<String,String> map = JSON.parseObject(jsonStr,new TypeReference<Has ...

  2. Python核心技术与实战——二十|Python的垃圾回收机制

    今天要讲的是Python的垃圾回收机制 众所周知,我们现在的计算机都是图灵架构.图灵架构的本质,就是一条无限长的纸带,对应着我们的存储器.随着寄存器.异失性存储器(内存)和永久性存储器(硬盘)的出现, ...

  3. UVA - 1649 Binomial coefficients (组合数+二分)

    题意:求使得C(n,k)=m的所有的n,k 根据杨辉三角可以看出,当k固定时,C(n,k)是相对于n递增的:当n固定且k<=n/2时,C(n,k)是相对于k递增的,因此可以枚举其中的一个,然后二 ...

  4. Observer-Proxy拦截器 -ES6

    在目标对象前嫁接了一个拦截层,外界对该对象的访问都必须通过这层拦截 可实现观察者模式

  5. WebKitBrowser

    WebKit.net是对WebKit的.Net封装, 使用它.net程序可以非常方便的集成和使用webkit作为加载网页的容器. 首先 下载WebKit.net 的bin文件. 然后 新建一个Wind ...

  6. qt5--QEvent事件

    QEvent事件是负责事件分发,包括所有事件返回值为true,用户自己处理事件,不向下分发:false系统处理事件---必须有返回值 查看所有事件,在Qt助手中搜索QEvent::Type #incl ...

  7. nodejs之express入门

    首先安装nodejs,官网下载安装包直接安装, 由于node自带npm,所以npm命令直接即可使用 打开cmd,使用npm install -g express-generator安装express ...

  8. Eclipse中文件结构的树形显示问题

    问题描述:在Eclipse中的SpringBoot文件显示层级消失. 这种情况下编辑代码的效率大大下降. 原因:Eclipse的工作模式不正确.上面的工作模式是Java模式.实际上应采用JavaEE模 ...

  9. Unity3D_(插件)DOTween动画插件

    使用DOTween动画插件来实现物体的移动动画 Learn 一.DOTween插件对变量的动画 二.控制Cube和UI面板的动画 三.动画的快捷播放方式 四.动画的前放和后放 五.From Tween ...

  10. Android_(服务)Vibrator振动器

    Vibrator振动器是Android给我们提供的用于机身震动的一个服务,例如当收到推送消息的时候我们可以设置震动提醒,也可以运用到游戏当中增强玩家互动性 运行截图: 程序结构 <?xml ve ...