今天我会将学习Java多线程的做个笔记,方便以后复习。
自定义一个线程(extends Thread)
class MyThread extends Thread {
@Override
public void run() {
for(int i = 0 ; i < 5 ; i++)
System.out.println(this.getName() +"--->"+i);
super.run();
}
}
主函数
MyThread thread = new MyThread();
MyThread thread1 = new MyThread();
thread.start();
thread1.start();
try {
Thread.sleep(1000);
System.out.println("------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.run();
thread1.run();
运行结果:
Thread-0--->0
Thread-1--->0
Thread-0--->1
Thread-1--->1
Thread-0--->2
Thread-1--->2
Thread-0--->3
Thread-1--->3
Thread-0--->4
Thread-1--->4
------------------
Thread-0--->0
Thread-0--->1
Thread-0--->2
Thread-0--->3
Thread-0--->4
Thread-1--->0
Thread-1--->1
Thread-1--->2
Thread-1--->3
Thread-1--->4
当run方式时,其实并非多线程,只是普通的执行了方法而已,start方法才是符合多线程。为什么?看源码
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
看到start0方法,有native进行修饰。简单地讲,一个native Method就是一个java调用非java代码的接口。一个native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。因为我们知道多线程是与操作系统有关的。
自定义一个线程(implements Runnable)
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0 ; i < 5 ; i++)
System.out.println(this.hashCode()+"--->"+i);
}
}
主函数
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
t1.start();
t2.start();
结果我就不演示了。两者有什么区别,看接下来的小程序就明白了
class MyThread extends Thread {
private int num;
public MyThread(int num){
this.num = num;
}
@Override
public void run() {
while(num >= 0 )
System.out.println(this.getName()+"num数量--->"+(num--));
super.run();
}
}
class MyRunnable implements Runnable{
private int num;
public MyRunnable(int num){
this.num = num;
}
@Override
public void run() {
while(num >= 0 )
System.out.println(Thread.currentThread().getName()+"num数量--->"+(num--));
}
}
public class Test {
public static void main(String[] args) {
int num = 3;
MyThread thread = new MyThread(num);
MyThread thread1 = new MyThread(num);
thread.start();
thread1.start();
try {
Thread.sleep(1000);
System.out.println("------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
MyRunnable mRun = new MyRunnable(num);
Thread t1 = new Thread(mRun);
Thread t2 = new Thread(mRun);
t1.start();
t2.start();
}
}
结果
Thread-0num数量--->3
Thread-0num数量--->2
Thread-0num数量--->1
Thread-0num数量--->0
Thread-1num数量--->3
Thread-1num数量--->2
Thread-1num数量--->1
Thread-1num数量--->0
------------------
Thread-2num数量--->3
Thread-3num数量--->2
Thread-3num数量--->0
Thread-2num数量--->1
通过可以看到runnable对于num进行了我们想要的操作,两个线程操作统一个共享资源。就好像不同的售票窗口去买数据库总的票一样。总结一下Runnable的优点。
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
看Thread的源码你会发现继承了Runnable接口
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}...
...
}
线程的相关方法
public final native boolean isAlive();//是否活着
/*
*join方法的功能就是使异步执行的线程变成同步执行
*使用join方法后,直到这个线程退出,程序才会往下执行。
*/
public final synchronized void join(int millis );//等待该线程终止的时间最长为 millis 毫秒。
public void interrupt();//中断
/**
*不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源
* public final static int MIN_PRIORITY = 1;
* public final static int NORM_PRIORITY = 5;
* public final static int MAX_PRIORITY = 10;
*/
public final void setPriority(int newPriority);
/**
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
*/
public static native void yield();
看一下一般线程的生命周期图
守护线程和用户线程
这里我具体讲一下守护线程和用户线程 。
所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。
在使用守护线程时需要注意一下几点:
(1)thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2)在Daemon线程中产生的新线程也是Daemon的。
(3)守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
class DaemonRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始运行");
// 每隔一秒钟显示守护线程在运行
int count = 1000;
while (count > 0 && Thread.currentThread().isAlive()) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "正在运行");
count--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "结束运行");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("主线程开始运行");
Thread thread = new Thread(new DaemonRunnable(), "守护线程");
thread.setDaemon(true);
thread.start();
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程退出运行");
}
}
结果是当主线程作为唯一的用户线程退出后,守护线程也就退出,不会将“守护线程正在运行”打印1000次
主线程开始运行
守护线程开始运行
守护线程正在运行
守护线程正在运行
守护线程正在运行
守护线程正在运行
主线程退出运行
同步问题(synchronized)
有问题的程序
class MyRunnable implements Runnable {
private int num;
public MyRunnable(int num) {
this.num = num;
}
@Override
public void run() {
while (num >= 0) {
/*try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println(Thread.currentThread().getName() + "num数量--->" + (num--));
}
}
}
public class Test {
public static void main(String[] args) {
int num = 5;
MyRunnable mRun = new MyRunnable(num);
Thread t1 = new Thread(mRun);
Thread t2 = new Thread(mRun);
Thread t3 = new Thread(mRun);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
运行n遍后的结果:
线程2num数量--->5
线程2num数量--->4
线程2num数量--->3
线程2num数量--->2
线程2num数量--->1
线程2num数量--->0
没有问题呀!真的没有嘛,其实不然,那是因为run占用的时间太短了,真实的run一定是有比较长的。把Thead.sleep()方法打开后,问题就来了:
线程2num数量--->5
线程3num数量--->4
线程1num数量--->3
线程2num数量--->2
线程3num数量--->1
线程1num数量--->0
线程2num数量--->-1
线程3num数量--->-2
为什么?因为多个线程可以操作num–,当线程1操作时num=0,可是线程2,线程3已经进入while(num>0)的判断中,当时num确实是>0的,所以出现了-1,-2的结果。如何处理?synchronized 当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码
class MyRunnable implements Runnable {
private int num;
public MyRunnable(int num) {
this.num = num;
}
@Override
public void run() {
des();
}
public synchronized void des() {
while (num >= 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "num数量--->" + (num--));
}
}
}
结果
线程2num数量--->5
线程2num数量--->4
线程2num数量--->3
线程2num数量--->2
线程2num数量--->1
线程2num数量--->0
下面浅显的讲一下线程的可见性和原子性,以及synchronized,volatile实现共享变量的原理
首先了解一下Java内存模型(JMM):围绕着在并发过程中如何处理可见性、原子性、有序性这三个特性而建立的模型。
可见性:JMM提供了volatile变量定义、final、synchronized块来保证可见性。
1.synchronized如何实现可见性:
在加锁前,清空工作内存中的共享变量的值,重新从主内存中去最新的共享变量的值。
在解锁前,将最新的工作内存中的共享变量的值刷到主内存中。
2.volatile如何实现可见性
深入的讲:volatile通过加入内存屏障和禁止指令重排序的方法实现可见性。
通俗的讲:volatile修饰的变量,每次被线程访问时都要从主内存中去,当变量发生变化时,都要强制刷的主内存中。
3.final如何实现可见性:
这个最简单,所有的final变量都是不可变的,当然是可见的
原子性:JMM提供保证了访问基本数据类型的原子性(其实在写一个工作内存变量到主内存是分主要两步:store、write),但是实际业务处理场景往往是需要更大的范围的原子性保证,所以模型也提供了synchronized块来保证。对比一下下面两个例子
public class Test {
public volatile int num = 0;
public void inc() {
num++;
}
public static void main(String[] args) {
Test test = new Test();
for (int i = 0; i < 500; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
test.inc();
}
});
thread.start();
}
// 如果还有子线程在运行,主线程就让出CPU资源,
// 直到所有的子线程都运行完了,主线程再继续往下执行
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("num = " + test.num);
}
}
多运行几遍你会发现
num = 499
这是因为volatile不具有原子性,num++分为3个步骤:取num,num+1,num赋值回来。执行到任何一步都有可能被别的线程插入进来。导致结果num<=500。synchronized则不是这样,把这3个步骤看做一个原子操作。没有执行完是不会被别的线程抢占执行的。num一定等于500
public class Test {
public int num = 0;
public synchronized void inc() {
num++;
}
public static void main(String[] args) {
Test test = new Test();
for (int i = 0; i < 500; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
test.inc();
}
});
thread.start();
}
// 如果还有子线程在运行,主线程就让出CPU资源,
// 直到所有的子线程都运行完了,主线程再继续往下执行
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("num = " + test.num);
}
}
结果
num = 500
其实多线程的内容还有很多。这篇博客的内容也是我在学习多线程的一些笔记而已。我也参考了一些资料。作为以后温习用吧!有不对的地方请多多指教。