java基础回顾(五)线程详解以及synchronized关键字
本文将从线程的使用方式、源码、synchronized关键字的使用方式和陷阱以及一些例子展开java线程和synchronized关键字的内容。
一、线程的概念
线程就是程序中单独顺序的流控制。线程本 身不能运行,它只能用于程序中。
二、线程的实现
线程的实现有两种方式:
1.继承Thread类并重写run方法
2.通过定义实现Runnable接口的类进而实现run方法
当用第一种方式时我们需要重写run方法因为Thread类里的run方法什么也不做(见下边的源码),当用第二种方式时我们需要实现Runnable接口的run方法,然后使用new Thread(new Runnable())来生成线程对象,这时的线程对象的run方法就会调用Runnable的run方法,这样我们自己编写的run方法就执行了。
将我们希望线程执行的代码放在run方法中,然后通过start方法来启动线程,start方法首先为线程的执行准备好系统资源,然后再去调用run方法。下边例子中将会有代码。
在jdk源码里start方法里面调用了start0()方法,他是一个native方法,我们不可见。线程一经运行就不会受我们的控制,Thtead类里其实有stop方法,但是不能通过挑用stop()方法,而是应该让run方法自然结束。
有一些需要注意的地方用代码举例说明:
例一:
public class ThreadTest2 {
public static void main(String[] args) {
// Thread thread = new Thread(new Runnable() {
// @Override
// public void run() {
// for(int i = 0; i < 100; i++) {
// System.out.println("hello" + i);
// }
// }
// }); MyThread myThread = new MyThread();
Thread thread = new Thread(myThread); thread.start();
}
} class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("hello" + i);
}
}
}
上面的代码是采用第二种方式通过定义实现Runnable接口的类进而实现run方法,重点看注释掉的部分当生命一个简单的Thread类时用匿名内部类来实现是一个比较好的方式。
例二:
public class ThreadTest3 {
public static void main(String[] args) {
Runnable r = new Thread3();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r); t1.start();
t2.start();
}
} class Thread3 implements Runnable { int i; @Override
public void run() { // i是成员变量和局部变量的结果是不一样的
// 对于i是成员变量的时候,所有线程共享这一个成员变量,不管有几个线程只要i加到了10就会终止线程,所以最后的结果一定只有10个。
// 对于i是一个局部变量时,每个线程都会有一份局部变量的拷贝,并且线程与线程之间是互不影响的
// int i = 0; while(true) {
System.out.println("number = " + i++); try {
Thread.sleep((long)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
} if(10 == i) {
break;
}
}
}
}
关于成员变量与局部变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对成员变量是彼此影响的(也就是说一个线程对成员变量的海边会影响到另一个线程)。如果一个变量是局部变量那么每个线程都会有一个该局部变量的拷贝,一个线程对局部变量的改变是不会影响到其他线程的。
例三:
public class ThreadTest4 {
public static void main(String[] args) {
Runnable r = new Thread4();
Thread t1 = new Thread(r); // 重新声明一个对象的话,就又会打印20个结果。这是因为不是原来的对象了相对应的成员变量是分别在两个对象里的,所以当然也是不会相互影响的。所以会打印20个结果。
// r = new Thread4();
Thread t2 = new Thread(r); t1.start();
t2.start();
}
} class Thread4 implements Runnable { int i; @Override
public void run() { while(true) {
System.out.println("number = " + i++); try {
Thread.sleep((long)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
} if(10 == i) {
break;
}
}
}
}
结果是输出number = 0到number = 9而且只顺序输出一遍,但是如果没有注释掉r = new Thread4();的话程序就会交替输出两遍number = 0到number = 9。其实就是因为重新声明了对象两个线程作用的成员变量不是一个,从而会输出两次。
三、synchronized关键字(重点介绍)
为什么要引入同步机制?
在多线程环境中,可能会有两个甚至多个线程试图同时访问一个有限的资源。这时就会发生许多意想不到的资源冲突,必须对这种冲突进行预防由此引入同步机制。
synchronize关键字简介:当synchronize关键字修饰一个方法时,该方法叫做同步方法。java中的每个对象都有一个锁(或者叫做监视器),当访问某个对象的synchronize方法时,表示将该对象上锁(不是方法),此时其他任何线程都无法再去访问该方法了,直到之前的那个线程执行方法完毕后或者是抛出了异常,那么将该对象的锁释放掉,其他线程才能再去访问该synchronize方法。这样就实现了方法的单线程访问。
- 在方法前用synchronized关键字修饰。
- 使用synchronized代码块。
接下来我们用实例来看一看synchronized关键字的特性以及工作方式
例一:
public class ThreadTest5 {
public static void main(String[] args) {
Example example = new Example();
Thread t1 = new TheThread(example);// example = new Example(); //加上这行代码的话,两个线程就会交替执行,说明synchronize关键字是作用于对象层面上的。
Thread t2 = new TheThread2(example); t1.start();
t2.start();
}
} class Example { public synchronized void execute() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("hello: " + i);
}
} public synchronized void execute2() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("world: " + i);
}
}
} class TheThread extends Thread {
private Example example; public TheThread(Example example) {
this.example = example;
} @Override
public void run() {
this.example.execute();
}
} class TheThread2 extends Thread {
private Example example; public TheThread2(Example example) {
this.example = example;
} @Override
public void run() {
this.example.execute2();
}
}
执行结果先顺序输出hello: 0到hello: 19 再顺序输出world: 0到world: 19
需要注意的是如果重新生成一个Example对象的话就不是顺序输出了而是交替不规则的输出了:
只截取了部分数据,之所以交替输出是因为synchronized关键字是作用于对象上的,两个线程调用两个不同的对象的synchronized方法不会发生抢占,所以两个线程同时进行。只有一个对象的时候,如果一个对象有多个synchronize方法,某一个时刻某个线程已经进入到了某个synchronize方法,那么在该方法没有执行完毕前,其他线程是无法访问到该对象的任何synchronize方法的。所以是顺序输出。
例二:
public class ThreadTest6 {
public static void main(String[] args) {
Example2 example = new Example2();
Thread t1 = new TheThread3(example);
example = new Example2();
Thread t2 = new TheThread4(example); t1.start();
t2.start();
}
} class Example2 {
// synchronized修饰的方法如果是静态方法那么synchronized就不是作用于方法所在的对象了,而是方法所在对象的class对象。也就是类本身,对象有多个但是对象所对应的的class对象肯定是只有一个
public synchronized static void execute() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("hello: " + i);
}
} public synchronized static void execute2() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("world: " + i);
}
}
} class TheThread3 extends Thread {
private Example2 example; public TheThread3(Example2 example) {
this.example = example;
} @Override
public void run() {
this.example.execute();
}
} class TheThread4 extends Thread {
private Example2 example; public TheThread4(Example2 example) {
this.example = example;
} @Override
public void run() {
this.example.execute2();
}
}
例二的执行结果是 先顺序输出hello: 0到hello: 19 再顺序输出world: 0到world: 19
这时候就奇怪了,例二和例一(没有注释第五行代码的情况下)的区别只有static修饰的区别怎么结果完全不一样。
如果某个synchronize方法是static的,那么当线程访问该方法时,他的锁并不是synchronize方法所在的对象,而是synchronize方法所在的对象所对应的的class对象,因为java中无论一个类有多少个对象,这些对象只会唯一对应一个class对象,因此当线程分别访问同一个类的两个对象的两个static synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。
例三:
public class ThreadTest7 {
public static void main(String[] args) {
Example3 e = new Example3();
TheThread5 t1 = new TheThread5(e);
// e = new Example3();
TheThread6 t2 = new TheThread6(e); t1.start();
t2.start();
}
} class Example3 {
// 没有实际意义,任何一个对象都行,不写也没事用this就好。
private Object object = new Object(); public void execute() {
// synchronized代码块
synchronized (this) {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("hello: " + i);
}
} } public void execute2() {
synchronized (this) {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("world: " + i);
}
} }
} class TheThread5 extends Thread {
private Example3 example; public TheThread5(Example3 example) {
this.example = example;
} @Override
public void run() {
this.example.execute();
}
} class TheThread6 extends Thread {
private Example3 example; public TheThread6(Example3 example) {
this.example = example;
} @Override
public void run() {
this.example.execute2();
}
}
这个例子主要说明的是synchronized关键字第二种用法:synchronized代码块。
上面已经介绍过了synchronized代码块是一种细粒度的并发控制,只会将代码块中的代码同步,位于方法内,synchronized块之外的方法里的代码是可以被多个线程同时访问到的。相对于synchronized代码块来说synchronized方法是一种更加重量级的并发控制机制。
好啦今天就这些吧,洗洗睡了。。。
java基础回顾(五)线程详解以及synchronized关键字的更多相关文章
- Java基础-面向接口编程-JDBC详解
Java基础-面向接口编程-JDBC详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.JDBC概念和数据库驱动程序 JDBC(Java Data Base Connectiv ...
- java基础(3)--详解String
java基础(3)--详解String 其实与八大基本数据类型一样,String也是我们日常中使用非常频繁的对象,但知其然更要知其所以然,现在就去阅读源码深入了解一下String类对象,并解决一些我由 ...
- java基础6 面向对象的详解
本文知识点(目录): 1.1.万物皆对象 1.2.面向对象的概述 1.3.面向对象(java语言)与面向过程(C语言)对比 1.4.面向过程 1.5.对象 1.6.面向对 ...
- 【Java基础】HashMap原理详解
哈希表(hash table) 也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,本文会对java集合框架中Has ...
- Java基础(44):ArrayList使用详解
1.什么是ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处: a.动态的增加和减少元素 b.实现了IColle ...
- Java基础13:反射详解
本节主要介绍Java反射的原理,使用方法以及相关的技术细节,并且介绍了关于Class类,注解等内容. 具体代码在我的GitHub中可以找到 https://github.com/h2pl/MyTech ...
- java基础回顾(五)——Stack、Heap
栈(stack):是简单的数据结构,但在计算机中使用广泛.栈最显著的特征是:LIFO(Last In, First Out,后进先出).比如我们往箱子里面放衣服,先放入的在最下方,只有拿出后来放入的才 ...
- Java基础(55):Exception类详解(转)
Java中的异常 Exception java.lang.Exception类是Java中所有异常的直接或间接父类.即Exception类是所有异常的根类. 比如程序: public class Ex ...
- java基础之类与继承 详解
Java:类与继承 对于面向对象的程序设计语言来说,类毫无疑问是其最重要的基础.抽象.封装.继承.多态这四大特性都离不开类,只有存在类,才能体现面向对象编程的特点,今天我们就来了解一些类与继承的相关知 ...
随机推荐
- Charles Proxy 4.1.2 破解版
下载Charles Proxy 4.1.2版本,百度云盘下载 或 去官网下载 安装后先打开Charles一次(Windows版可以忽略此步骤) 在这个网站(http://charles.iiilab. ...
- AFNetworking 动态修改acceptableContentTypes 设置ContentType
AFJSONResponseSerializer+Serializer.h #import <AFNetworking/AFNetworking.h> @interface AFJSONR ...
- 【charger battery 充電 充電器 電池】停充的種類
Precondition : 配有 power path 功能的 BQ2589 手機. 接上 pc usb port. Origin : 今天有同事問我, 手機是否可以在接上 pc usb port ...
- ASP.NET Core MVC 模型绑定用法及原理
前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC ...
- 磁盘分区-gdisk用法
gdisk用法 gdisk - InteractiveGUIDpartitiontable (GPT) manipulator GPTfdisk (akagdisk) isatext-modemenu ...
- 彻底清除Linux centos minerd木马
前几天,公司两台linux服务器,一台访问速度很慢,cpu跑满,一台免密码登录失效,公钥文件被改写成redis的key.用htop命令查询发现了minerd木马进程,初步猜测是redis没有配访问权限 ...
- neutron flat和vxlan网络访问外网流量走向
OpenStack版本:Mitaka 物理节点: Hostname Management IP Tunnel IP Role test-ctrl-01 192.168.100.11 192.168.1 ...
- 小程序API录音后Silk格式转码MP3
问题 客户端使用小程序,需要录音功能然后到后台页面播放,由于微信提供的录音API压缩后的格式为 .silk格式的,但是这个格式其他播放器都是播放不了的,更何况html页面的audio标签更是不可能播放 ...
- Scheme 中的 pair 和 list 简述
pair (cons 1 2) > (1 . 2) 系统返回(1 . 2).cons 操作给两个地址分配了内存空间,并把存放指向 1 的地址放在一个空间,把存放指向2的地址放在另一个空间.存放指 ...
- 关于QT5使用QtScript解析QJsonArray数组的问题
首先得在pro文件中加入QT+=script 然后导入相应的头文件 include <QStringList> #include <QtScript/QScriptEngine> ...