本文将从线程的使用方式、源码、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关键字有两种使用方法:
  1. 在方法前用synchronized关键字修饰。
  2. 使用synchronized代码块。
synchronized方法是一种粗粒度的并发控制,某一时刻只能有一个线程执行该synchronized方法,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关键字的更多相关文章

  1. Java基础-面向接口编程-JDBC详解

    Java基础-面向接口编程-JDBC详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.JDBC概念和数据库驱动程序 JDBC(Java Data Base Connectiv ...

  2. java基础(3)--详解String

    java基础(3)--详解String 其实与八大基本数据类型一样,String也是我们日常中使用非常频繁的对象,但知其然更要知其所以然,现在就去阅读源码深入了解一下String类对象,并解决一些我由 ...

  3. java基础6 面向对象的详解

    本文知识点(目录): 1.1.万物皆对象    1.2.面向对象的概述    1.3.面向对象(java语言)与面向过程(C语言)对比    1.4.面向过程    1.5.对象    1.6.面向对 ...

  4. 【Java基础】HashMap原理详解

    哈希表(hash table) 也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,本文会对java集合框架中Has ...

  5. Java基础(44):ArrayList使用详解

    1.什么是ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处:    a.动态的增加和减少元素    b.实现了IColle ...

  6. Java基础13:反射详解

    本节主要介绍Java反射的原理,使用方法以及相关的技术细节,并且介绍了关于Class类,注解等内容. 具体代码在我的GitHub中可以找到 https://github.com/h2pl/MyTech ...

  7. java基础回顾(五)——Stack、Heap

    栈(stack):是简单的数据结构,但在计算机中使用广泛.栈最显著的特征是:LIFO(Last In, First Out,后进先出).比如我们往箱子里面放衣服,先放入的在最下方,只有拿出后来放入的才 ...

  8. Java基础(55):Exception类详解(转)

    Java中的异常 Exception java.lang.Exception类是Java中所有异常的直接或间接父类.即Exception类是所有异常的根类. 比如程序: public class Ex ...

  9. java基础之类与继承 详解

    Java:类与继承 对于面向对象的程序设计语言来说,类毫无疑问是其最重要的基础.抽象.封装.继承.多态这四大特性都离不开类,只有存在类,才能体现面向对象编程的特点,今天我们就来了解一些类与继承的相关知 ...

随机推荐

  1. POJ1850-Code 递推数学

    题目链接:http://poj.org/problem?id=1850 题目大意: 按照字典序对升序排列组成的字母进行编号,给出一个长度不超过10的串,求出它的编号是多少?如果无法进行编号则输出0. ...

  2. [笔记]cin、cout与scanf、printf的效率差异对比分析

    之前上传UVa227 puzzle时,好不容易AC了,但发现自己用时50(ms),而在VJ上看到人家都是40ms.20ms,于是打开一个20ms的代码查看人家强在哪里.但结果研究了半天感觉差不多,于是 ...

  3. 解决U盘容量变小问题

    今天又想重新给电脑刷刷kali linux新版本了貌似N久没更,直接重新刷系统吧...然后发现USB容量变小,这就尴尬了,接着总结了个小方法. 解决方法:1.先把u盘插好,运行cmd,2.输入disk ...

  4. 【2017-05-21】WebForm内置对象:Session、Cookie,登录和状态保持

    1.Request -获取请求对象 string s =Request["key"]; 2.Response  -  响应请求对象 Response.Redirect(" ...

  5. C语言习题1.分别统计一下其中字母,数字,其他字符的个数。将统计的字母,数字,其他字符的个数以柱状图的形式打印

    从键盘上输入字符,(1)分别统计一下其中字母,数字,其他字符的个数, (2)将统计的字母,数字,其他字符的个数以柱状图的形式打印.例如 5 ***** *****     3 *****   **** ...

  6. CentOS-Zabbix-agent客户端的编译安装

    系统环境: CentOS 6.7 官网下载安装包:http://www.zabbix.com/download 本文用的是Zabbix 3.0 LTS 上传至客户端服务器并解压 .tar.gz 进入解 ...

  7. 互联网金融P2P主业务场景自动化测试

            互联网金融P2P行业,近三年来发展迅速,如火如荼.         据不完全统计,全国有3000+的企业.  “互联网+”企业,几乎每天都会碰到一些奇奇怪怪的bug,作为在互联网企业工 ...

  8. Nodejs进阶:核心模块Buffer常用API使用总结

    本文摘录自<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. 模块概览 Buffer是node的核心模块,开发者可以利用它来处 ...

  9. 项目管理之 Git 管理软件 SourceTree for Mac

    Git 项目管理: Mac Terminal 生成 Git 秘钥流程: git config --global user.name "yourname" git config -- ...

  10. 初码-Azure系列-文章目录

    系统迁移 初码-Azure系列-记一次MySQL数据库向Azure的迁移 初码-Azure系列-迁移PHP应用至Azure的一些实践记录和思考 初码-Azure系列-记一次从阿里云到Azure的迁移和 ...