java多线程基础(synchronize关键字)
基础知识
线程:进程(process)就是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。
线程:进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。
线程和进程的区别如下:
1)一个进程至少有一个线程。线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
2)线程在执行过程中与进程的区别在于每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
3)从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用来实现进程的调度和管理以及资源分配。
2 简述线程的状态及其转换
1)New,创建一个线程,但是线程并没有进行任何的操作。
2)Runnable,新线程从New状态,调用start方法转换到Runnable状态。线程调用start方法向线程调度程序(JVM或者是操作系统)注册一个线程,这个时候一切就绪只等CPU的时间。
3)Running,从Runnable状态到Running状态,线程调度根据调度策略的不同调度不同的线程,被调度执行的线程进入Running状态,执行run方法。
4)Dead状态,从Running状态到Runnable,run方法运行完毕后,线程就会被抛弃,线程就进入Dead状态。
5)Block状态,从Running状态到Block状态,如果线程在运行的状态中因为I/O阻塞、调用了线程的sleep方法以及调用对象的wait方法则线程将进入阻塞状态,直到这些阻塞原因被结束,线程进入到Runnable状态。
3 简述线程的两种创建方式以及它们的区别
创建线程的两种方式:
1)使用Thread创建线程。Thread类是线程类,其每一个实例表示一个可以并发运行的线程。我们可以通过继承该类并重写run方法来定义一个具体的线程。其中重写run方法的目的是定义该线程要执行的逻辑。启动线程时调用线程的start()方法而非直接调用run()方法。start()方法会将当前线程纳入线程调度,使当前线程可以开始并发运行。当线程获取时间片段后会自动开始执行run方法中的逻辑。
2)使用Runnable创建线程。实现Runnable接口并重写run方法来定义线程体,然后在创建线程的时候将Runnable的实例传入并启动线程。
两种创建线程方式的区别:
使用Thread创建线程,编写简单,可以直接操纵线程,无需使用Thread.currentThread(),但是不能够再继承其他类。
使用Runnable创建线程可以将线程与线程要执行的任务分离开减少耦合,同时Java是单继承的,定义一个类实现Runnable接口,这样该类还可以继承自其他类。
多线程实现方法
使用Thread创建线并启动线程
java.lang.Thread类是线程类,其每一个实例表示一个可以并发运行的线程。我们可以通过继承该类并重写run方法来定义一个具体的线程。其中
重写run方法的目的是定义该线程要执行的逻辑。启动线程时调用线程的start()方法而非直接调用run()方法。start()方法会将当前线程纳入线程调
度,使当前线程可以开始并发运行。当线程获取时间片段后会自动开始执行run方法中的逻辑。
public class TestThread extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("我是线程");
}
}
}
创建预启动线程
…
Thread thread = new TestThread();//实例化线程
thread.start();//启动线程
…
使用Runnable创建并启动线程
实现Runnable接口并重写run方法来定义线程体,然后在创建线程的时候将Runnable的实例传入并启动线程。
这样做的好处在于可以将线程与线程要执行的任务分离开减少耦合,同时java是单继承的,定义一个类实现Runnable接口这样的做法可以更好的
去实现其他父类或接口。因为接口是多继承关系。
public class TestRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("我是线程");
}
}
}
启动线程的方法:
…
Runnable runnable = new TestRunnable();
Thread thread = new Thread(runnable);//实例化线程并传入线程体
thread.start();//启动线程
…
使用内部类创建线程
通常我们可以通过匿名内部类的方式创建线程,使用该方式可以简化编写代码的复杂度,当一个线程仅需要一个实例时我们通常使用这种方式来
创建。
例如:
继承Thread方式:
Thread thread = new Thread(){ //匿名类方式创建线程
public void run(){
//线程体
}
};
thread.start();//启动线程
Runnable方式:
Runnable runnable = new Runnable(){ //匿名类方式创建线程
public void run(){
}
};
Thread thread = new Thread(runnable);
thread.start();//启动线程
线程的方法
currentThread:方法可以用于获取运行当前代码片段的线程
Thread current = Thread.currentThread();
获取线程信息
Thread提供了 获取线程信息的相关方法:
long getId():返回该线程的标识符
String getName():返回该线程的名称
int getPriority():返回线程的优先级
Thread.state getState():获取线程的状态
boolean isAlive():测试线程是否处于活动状态
boolean isDaemon():测试线程是否为守护线程
boolean isInterrupted():测试线程是否已经中断
线程优先级
线程的切换是由线程调度控制的,我们无法通过代码来干涉,但是我们可以通过提高线程的优先级来最大程度的改善线程获取时间片的几率。
线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了3个常量来表示最低,最高,以及默认优先级:
Thread.MIN_PRIORITY,
Thread.MAX_PRIORITY,
Thread.NORM_PRIORITY
设置优先级的方法为:
void setPriority(int priority)
守护线程
守护线程与普通线程在表现上没有什么区别,我们只需要通过Thread提供的方法来设定即可:
**void setDaemon(boolean )**
当参数为true时该线程为守护线程。
守护线程的特点是,当进程中只剩下守护线程时,所有守护线程强制终止。
GC就是运行在一个守护线程上的。
需要注意的是,设置线程为后台线程要在该线程启动前设置。
Thread daemonThread = new Thread();
daemonThread.setDaemon(true);
daemonThread.start();
sleep方法
Thread的静态方法sleep用于使当前线程进入阻塞状态:
**static void sleep(long ms)**
该方法会使当前线程进入阻塞状态指定毫秒,当指定毫秒阻塞后,当前线程会重新进入Runnable状态,等待分配时间片。
该方法声明抛出一个InterruptException。所以在使用该方法时需要捕获这个异常
**注:**改程序可能会出现"跳秒"现象,因为阻塞一秒后线程并非是立刻回到running状态,而是出于runnable状态,等待获取时间片。那么这段等待
时间就是"误差"。所以以上程序并非严格意义上的每隔一秒钟执行一次输出。
yield方法:
Thread的静态方法yield:
**static void yield()**
该方法用于使当前线程主动让出当次CPU时间片回到Runnable状态,等待分配时间片。
join方法
**void join()**
该方法用于等待当前线程结束。此方法是一个阻塞方法。
该方法声明抛出InterruptException。
线程同步
synchronized关键字
多个线程并发读写同一个临界资源时候会发生"线程并发安全问题“
常见的临界资源:
多线程共享实例变量
多线程共享静态公共变量
若想解决线程安全问题,需要将异步的操作变为同步操作。
所谓异步操作是指多线程并发的操作,相当于各干各的。
所谓同步操作是指有先后顺序的操作,相当于你干完我再干。
同步代码块(synchronized 关键字 ),同步代码块包含两部分:一个作为锁的对象的引用,一个作为由这个锁保护的代码块
这个比较难理解故写了下面代码帮助理解
/**
* 多线程并发安全问题
* 当多个线程同时操作同一资源时,由于
* 线程切换时机不确定,导致出现逻辑混乱。
* 严重时可能导致系统崩溃。
* @author ylg
*
*/
public class SyncDemo1 {
public static void main(String[] args) {
/*
* 当一个方法中的局部内部类想引用该方法
* 的其他局部变量时,这个变量必须被声明
* 为final的
*/
final Table table = new Table();
Thread t1 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
Thread.yield();//模拟线程切换
System.out.println(
getName()+":"+bean
);
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
Thread.yield();//模拟线程切换
System.out.println(
getName()+":"+bean
);
}
}
};
t1.start();
t2.start();
}
}
class Table{
//20个豆子
private int beans = 20;
/**
* 当一个方法被synchronized修饰后,该方法
* 成为"同步方法"。多个线程不能同时进入到
* 方法内部。
* @return
*/
public synchronized int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了!");
}
Thread.yield();//模拟线程切换
return beans--;
}
}
/**
* 有效的缩小同步范围可以保证在
* 安全的前提下提高了并发的效率
* @author ylg
*
*/
public class SyncDemo2 {
public static void main(String[] args) {
final Shop shop = new Shop();
Thread t1 = new Thread(){
public void run(){
shop.buy();
}
};
Thread t2 = new Thread(){
public void run(){
shop.buy();
}
};
t1.start();
t2.start();
}
}
class Shop{
/*
* 在方法上使用synchroinzed,同步监视器对象即当前方法所属对象:this
*/
// public synchronized void buy(){
public void buy(){
try{
Thread t = Thread.currentThread();
System.out.println(t+"正在挑选衣服..");
Thread.sleep(5000);
/*
* 同步块可以缩小同步范围。
* 但是必须保证"同步监视器"即:"上锁对象"是同一个才可以。
* 通常,在一个方法中使用this所谓同步监视器对象即可。
*/
synchronized (this) {
System.out.println(t+"正在试衣服..");
Thread.sleep(5000);
}
System.out.println(t+"结账离开");
}catch(Exception e){
}
}
}
/**
* synchronized也成为"互斥锁"
* 当synchronzed修饰的是两段代码,但是"锁对象"相同时,这两段代码就是互斥的。
* @author ylg
*
*/
public class SyncDemo4 {
public static void main(String[] args) {
final Boo b = new Boo();
Thread t1 = new Thread(){
public void run(){
b.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
b.methodB();
}
};
t1.start();
t2.start();
}
}
class Boo{
public synchronized void methodA(){
Thread t = Thread.currentThread();
System.out.println(t+"正在调用方法A");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println(t+"调用方法A完毕");
}
public synchronized void methodB(){
Thread t = Thread.currentThread();
System.out.println(t+"正在调用方法B");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println(t+"调用方法B完毕");
}
}```
**wait和notify**
多线程之间需要协调工作。
例如,浏览器的一个显示图片的 displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。如果图片还
没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示了”,这时,
displayThread继续执行。
以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于
wait/notify。等待机制与锁机制是密切关联的
java多线程基础(synchronize关键字)的更多相关文章
- [转]Java多线程干货系列—(一)Java多线程基础
Java多线程干货系列—(一)Java多线程基础 字数7618 阅读1875 评论21 喜欢86 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们 ...
- Java 多线程——基础知识
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- Java多线程基础知识总结
2016-07-18 15:40:51 Java 多线程基础 1. 线程和进程 1.1 进程的概念 进程是表示资源分配的基本单位,又是调度运行的基本单位.例如,用户运行自己的程序,系统就创建一个进程, ...
- Java 多线程基础(五)线程同步
Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...
- Java多线程基础:进程和线程之由来
转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...
- Java多线程--基础概念
Java多线程--基础概念 必须知道的几个概念 同步和异步 同步方法一旦开始,调用者必须等到方法调用返回后,才能执行后续行为:而异步方法调用,一旦开始,方法调用就立即返回,调用者不用等待就可以继续执行 ...
- Java基础16:Java多线程基础最全总结
Java基础16:Java多线程基础最全总结 Java中的线程 Java之父对线程的定义是: 线程是一个独立执行的调用序列,同一个进程的线程在同一时刻共享一些系统资源(比如文件句柄等)也能访问同一个进 ...
- 1、Java多线程基础:进程和线程之由来
Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...
- Java 多线程基础(一)基本概念
Java 多线程基础(一)基本概念 一.并发与并行 1.并发:指两个或多个事件在同一个时间段内发生. 2.并行:指两个或多个事件在同一时刻发生(同时发生). 在操作系统中,安装了多个程序,并发指的是在 ...
- Java 多线程基础(三) start() 和 run()
Java 多线程基础(三) start() 和 run() 通过之前的学习可以看到,创建多线程过程中,最常用的便是 Thread 类中的 start() 方法和线程类的 run() 方法.两个方法都包 ...
随机推荐
- cobbler简介+安装
(介绍部分的内容部分是借鉴网上的非原创) 回顾pxe+kickstart PXE PXE(preboot execute environment,预启动执行环境) PXE启动原理: 当计 ...
- log 的 debug()、 error()、 info()方法的区别
软件中总免不了要使用诸如 Log4net, Log4j, Tracer 等东东来写日志,不管用什么,这些东东大多是大同小异的,一般都提供了这样5个日志级别: × Debug × Info ...
- 18-EasyNetQ:发生错误的情况
这一篇文章让我们看看在消息系统中可能发生的各种错误的情况下,看下EasyNetQ如何处理它们. 订阅服务挂了 当你写了一个windows 服务,用来订阅一个NewCustomerMessage消息. ...
- Python爬虫从入门到放弃(十三)之 Scrapy框架的命令行详解
这篇文章主要是对的scrapy命令行使用的一个介绍 创建爬虫项目 scrapy startproject 项目名例子如下: localhost:spider zhaofan$ scrapy start ...
- 华为OJ之最长公共子序列
题目描述: 对于两个给定的字符串,给出他们的最长公共子序列. 题目分析: 1,在之前的博文(http://www.cnblogs.com/yonguo123/p/6711360.html)中我们讨论了 ...
- 第一章:火狐浏览器 : 环境配置: FireFox 版本38 + jdk 7 + selenium 2.53.6 + selenum-version 2.48.2
配置一套完整的 selenium + Java + Firefox38 环境: 1. 火狐浏览器的版本 : 38 2. JDK 安装 1.7 版本的 3. 安装 Python 的版本是 2.7 4. ...
- Datatables快速入门开发--一款好用的JQuery表格插件
博主是一个java后端程序员小白,前端技术会用但不精通,做后台的一些功能经常要涉及表格的展示,分页,搜索,排序等等一系列功能,在经历了一段时间的原始手段,开始接触并使用Datatables,一个jqu ...
- Java基础之String类
String类 字符串是不可变的,对其做的任何改变,会生成一个对象,不会改变有原有对象. ==和equals() String s1 = "good"; String s2 = & ...
- C#使用HttpClient获取Location
之前使用HttpWebRequest的时候,只需要设置HttpWebRequest对象的AllowAutoRedirect属性值为false即可在Respomse的Header中获取Location: ...
- 自己动手封装一个url参数解释器( ghostWuUrlParser.js )
ghostWuUrlParser.js的作用是分析一段url中的查询参数,即: '?'号后面的 键值对参数. ghostWuUrlParser.js 使用说明: ghostWuUrlParser( ' ...