深入讲解java线程与synchronized关键字

我们将会从以下的几点理解java线程的一些概念:

  1. 线程的基本概念和优劣之处
  2. 创建一个线程的两种方式
  3. 线程的属性
  4. 线程的状态
  5. synchronized可修饰的方法
  6. synchronized的重要特性

一、线程的基本概念

在计算机中有进程和线程这么两个概念,进程中可以有多个线程,它们是从属关系,进程往往更像是资源的占有者,线程才是程序的执行者,多个线程之间共享着进程中的资源。一个cpu同时只能运行一个线程,每个线程都有一个时间片,时间片用完了就会被阻塞并让出CPU的控制权,交给下一个线程使用。这样在计算机中就可以实现多任务的假象,其实CPU在不断的切换线程,好像多个任务在同时运行。

使用线程的优势毋庸置疑,可以增加CPU的执行效率,一旦某个线程需要等待某种资源(例如:等待打印机),就可以将它阻塞释放CPU让CPU执行别的线程,而不需要让CPU和此线程一起等待某种资源从而提高系统效率,另外一点就是可以用这种假象增加用户体验度。但是,CPU在切换不同线程之间所要花费的代价也是不可忽视的,在较为复杂的程序中这种劣势可能九流一毛,但是如果在简单的程序中就会显得尤为突出。

二、创建一个线程

接下来我们看看如何在java中创建一个线程来实现多个线程同时运行。第一种方式,java 中有一个类Thread,我们只要继承这个类并重写他的run方法,调用start方法就可以启动一个新的线程了。(没见过的同学可能不能理解以下代码,下面我会解释)

 /*声明自己的一个线程类*/
public class Test_thread extends Thread {
 //重写Thread类中的run方法
 public void run(){
 System.out.println("i am the thread");
 }
}

public class Test_Class {
 public static void main(String[] args){
 Test_thread thread = new Test_thread();
 thread.start();
 }
}

输出结果:i am the thread

首先我们先了解一下,一个线程被创建之后,怎么才能启动运行,我们调用thread.start();方法启动一个线程,首先就会执行我们重写的run方法(如果没有重写就会调用Thread类的run方法,什么也不做,这也是我们重写run方法的原因),也就是说run方法是一个线程的开始。有个疑问,为什么调用start方法,却执行了run方法了?其实调用start方法就是为线程的启动做准备操作,分配线程私有的堆栈资源,然后执行run方法。

下面我们看创建一个线程的第二种方式,实现接口Runnable,并重写其中的run方法。

 public class Test_thread implements Runnable {
 public void run(){
  System.out.println("i am the thread");
 }
}

public class Test_Class {
 public static void main(String[] args){
  Test_thread thread = new Test_thread();
  thread.start();//编译错误
 }
}

我们会发现虽然重写了run方法,但是在调用start方法的时候却编译错误,我们进入到Runnable接口的源代码中可以看到,只有一个抽象方法run,所以没有启动线程的start方法,所以我们还是要借助Tread类。

 public class Test_Class {
 public static void main(String[] args){
  Thread thread = new Thread(new Test_thread());
  thread.start();
 }
}

因为Thread类中有start方法,所以可以使用Thread的一个构造函数传入一个实现了Runnable接口的类型,构建一个Thread类。

三、线程的属性和状态

在一个多线程的程序中我们使用线程的一些属性来区别和辨认它们:

private long tid;
private volatile char name[];
public static native Thread currentThread()
private boolean  daemon = false;
private volatile int threadStatus = 0;
public final static int NORM_PRIORITY = 5;

每个线程会有一个tid指定此线程的在所有线程中的排序,这个值是递增的,还有一个name指定该线程的名字,也可以使用setName设置一个优雅的线程名字,Thread类还提供了一个方法返回当前正在占用CPU的线程对象。每个线程在某个时刻都是有状态的,属性threadStatus记录了当前对象线程的状态是什么,默认情况下,所有的线程的优先级都被置为5,如果有特殊需要可以修改线程的优先级,使得某个线程可以优先得到运行。下面介绍线程的几种不同的状态。

  1. New:线程刚刚被创建,还没有被启动。
  2. Runnable:线程处于可运行的状态,但是不一定处于正在运行的状态(后面解释区别)
  3. Blocked:线程处于被阻塞的状态,一般是请求某资源不成功于是让出CPU阻塞自己(具体的区别后面说)
  4. Waiting:线程处于等待状态,一般是等待服务
  5. Terminated:线程终止,也就是线程被kill,释放其占用的资源

下面具体的说说不同状态下的线程的一些操作,首先看看new,线程从此被创建,只是离运行状态还需要一些准备。Runnable表示线程是可运行,至于是否已经处于运行状态还要看系统给的时间片是否用完,如果用完了就会将此线程放置在可运行线程队列的尾部,等待下次分配时间片,如果时间片没有用完,就是处于运行状态的(这也是为什么叫做Runnable而不是Running的原因)。接下来的两种状态Blocked和waiting都表示线程阻塞,需要让出CPU。只是导致它们处于这种状态的原因不一样,具体的在我们介绍完synchronized关键字之后就会不言而喻了。

四、关键字synchronized

先看一段代码:

 public class Test_thread extends Thread{
 public static int count = 0;
 public void run(){
  try {
   Thread.sleep((int) (Math.random() * 100));
  }catch(InterruptedException e){

  }
  count++;
 }
}

public class Test_Class {
 public static void main(String[] args){
  Test_thread[] thread = new Test_thread[1000];
  for(int a=0;a<1000;a++){
   thread[a] = new Test_thread();
   thread[a].start();
  }

  for(int a=0;a<1000;a++){
   try {
    thread[a].join();
   }catch (InterruptedException e){

   }
  }
  System.out.println(Test_thread.count);
 }
}

按照直觉,创建1000个线程,每个线程随机睡觉并将公共变量增一,main线程等待所有线程执行结束之后,输出公共变量的值。按照直觉答案应该是1000,但是我们运行的结果每次都是不一样的,接近1000但每次都不一样。这是为什么呢?这其实就是简单的模拟了高并发,可能有几个线程睡了相同的时间,同时醒来获取的count值是相同的,这就导致这几个线程对count的操作被覆盖了。

 public class Test_thread extends Thread{
 public static int count = 0;
 public void run(){
  try {
   Thread.sleep((int) (Math.random() * 100));
  }catch(InterruptedException e){

  }
  /*使用关键字*/
  synchronized (Test_thread.class){
   count++;
  }
 }
}

一个简单的关键字就可以轻松解决这样的高并发的问题。至于为什么这么写?在介绍完synchronized关键字之后,想必你就会知道了。

首先我们需要知道,每个对象都有一把内部锁。所以被synchronized关键字修饰的方法,其实是被加了内部对象锁。我们看代码:

  public class Counter{
  private int count;

  /*为实例方法加此关键字*/
  public synchronized int getCount(){
   return count;
  }
 }

为实例方法添加关键字,实际上就是给当前的对象加锁;括号中就是要加锁的对象。

  public class Counter{
  private int count;

  /*为实例方法加此关键字*/
  synchronized(this){
   return count;
  }
 }

对于静态方法,实际上就是为这个类加上锁;

  public class Counter{
  private static int count;

  public static synchronized int getCount(){
   return count;
  }
 }

/*实际上给这个类加上锁*/
 public class Counter{
  private int count;

  synchronized(Counter.class){
   return count;
  }
 }

五、深入理解synchronized的一些特性

第一个性质是:可重入性。被synchronized修饰的方法中的所有操作都是原子操作,但是当我们需要在其中访问另外的一些需要锁的代码时候,可以直接获取别的锁。也就是当前的对象是可以获得多个锁的。

第二个性质是:内存的可见性。在我们的计算机中,其实有很多的操作并不是很"干脆"的,比如你向数据库中存数据时,其实很多时候一些待存入的数据积累在缓存中等到一定数据量的时候才会统一的存入数据库,但是在我们看来这些数据其实应该早就存入数据库了。这就导致了一个内存的不可见性问题。当然我们也是可以使用关键字synchronized来保证每次取出的数据都是最新的。在释放锁的时候会立即将数据协会内存,获得锁的时候会去读取最新的内容。

  public class Counter{
  private int count;

  public synchronized int getCount(){
   return count;
  }
 }
 /*每次获得该对象的锁之后,去获取最新的count数值*/

其实,如果仅仅是要保证内存的不可见性,使用synchronized关键字可能代价有点高,在这种情况下,我们可以使用关键字volatile。

  public class Counter{
  /*使用关键字volatile*/
  private volatile int count;

  public int getCount(){
   return count;
  }
 }
 /*此关键字保证每次使用count的时候数据都是最新的*/

总结

以上就是这篇文章的全部内容了,还是希望大家发现其中错误直接指出,方便我纠正错误,更新知识。java并发系列文章,希望大家多多关注。

(0)

相关推荐

  • java synchronized关键字的用法

    0.先导的问题代码 下面的代码演示了一个计数器,两个线程同时对i进行累加的操作,各执行1000000次.我们期望的结果肯定是i=2000000.但是我们多次执行以后,会发现i的值永远小于2000000.这是因为,两个线程同时对i进行写入的时候,其中一个线程的结果会覆盖另外一个. public class AccountingSync implements Runnable { static int i = 0; public void increase() { i++; } @Override

  • Java中synchronized关键字修饰方法同步的用法详解

    Java的最基本的同步方式,即使用synchronized关键字来控制一个方法的并发访问. 每一个用synchronized关键字声明的方法都是临界区.在Java中,同一个对象的临界区,在同一时间只有一个允许被访问. 静态方法则有不同的行为.用synchronized关键字声明的静态方法,同时只能够被一个执行线程访问,但是其他线程可以访问这个对象的非静态的synchronized方法.必须非常谨慎这一点,因为两个线程可以同时访问一个对象的两个不同的synchronized方法,即其中一个是静态s

  • 举例讲解Java中synchronized关键字的用法

    synchronized关键字顾名思义,是用于同步互斥的作用的. 这里精简的记一下它的使用方法以及意义: 1. 当synchronized修饰 this或者非静态方法或者是一个实例的时候,所同步的锁是加在this或者实例对象引用上面的.比如a,b同为Main类的实例化对象,a调用被同步的方法,和b调用被同步的方法,没有形成互斥.但是不同线程的a对象调用被同步的方法就被互斥了. public synchronized void method(){ //-. } public void method

  • Java多线程编程中synchronized线程同步的教程

    0.关于线程同步 (1)为什么需要同步多线程? 线程的同步是指让多个运行的线程在一起良好地协作,达到让多线程按要求合理地占用释放资源.我们采用Java中的同步代码块和同步方法达到这样的目的.比如这样的解决多线程无固定序执行的问题: public class TwoThreadTest { public static void main(String[] args) { Thread th1= new MyThread1(); Thread th2= new MyThread2(); th1.st

  • Java 多线程synchronized关键字详解(六)

    synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C. D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C .D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行.它包括两种用法:synchronized 方法和 synchronized 块. 多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多

  • 详解Java中synchronized关键字的死锁和内存占用问题

    先看一段synchronized 的详解: synchronized 是 java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以

  • java中synchronized(同步代码块和同步方法)详解及区别

     java中synchronized(同步代码块和同步方法)详解及区别 问题的由来: 看到这样一个面试题: //下列两个方法有什么区别 public synchronized void method1(){} public void method2(){ synchronized (obj){} } synchronized用于解决同步问题,当有多条线程同时访问共享数据时,如果进行同步,就会发生错误,Java提供的解决方案是:只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他

  • Java 同步锁(synchronized)详解及实例

    Java 同步锁(synchronized)详解及实例 Java中cpu分给每个线程的时间片是随机的并且在Java中好多都是多个线程共用一个资源,比如火车卖票,火车票是一定的,但卖火车票的窗口到处都有,每个窗口就相当于一个线程,这么多的线程共用所有的火车票这个资源.如果在一个时间点上,两个线程同时使用这个资源,那他们取出的火车票是一样的(座位号一样),这样就会给乘客造成麻烦.比如下面程序: package com.pakage.ThreadAndRunnable; public class Ru

  • Java多线程程序中synchronized修饰方法的使用实例

    在Java 5以前,是用synchronized关键字来实现锁的功能. synchronized关键字可以作为方法的修饰符(同步方法),也可作用于函数内的语句(同步代码块). 掌握synchronized,关键是要掌握把那个东西作为锁.对于类的非静态方法(成员方法)而言,意味着要取得对象实例的锁:对于类的静态方法(类方法)而言,要取得类的Class对象的锁:对于同步代码块,要指定取得的是哪个对象的锁.同步非静态方法可以视为包含整个方法的synchronized(this) { - }代码块.  

  • 深入讲解java线程与synchronized关键字

    我们将会从以下的几点理解java线程的一些概念: 线程的基本概念和优劣之处 创建一个线程的两种方式 线程的属性 线程的状态 synchronized可修饰的方法 synchronized的重要特性 一.线程的基本概念 在计算机中有进程和线程这么两个概念,进程中可以有多个线程,它们是从属关系,进程往往更像是资源的占有者,线程才是程序的执行者,多个线程之间共享着进程中的资源.一个cpu同时只能运行一个线程,每个线程都有一个时间片,时间片用完了就会被阻塞并让出CPU的控制权,交给下一个线程使用.这样在

  • Java中的synchronized关键字

    目录 1.synchronized锁的底层实现原理 2.基于synchronized实现单例模式 3.利用类加载实现单例模式(饿汉模式) 1.synchronized锁的底层实现原理 JVM基于进入和退出Monitor对象来实现方法同步和代码块同步.代码块同步是使用monitorenter和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处.任何对象都有一个monitor与之关联,当且一个moni

  • Java多线程并发synchronized 关键字

    目录 基础 修饰普通方法 修饰静态方法 Synchronized 加锁原理 monitorenter monitorexit synchronized 修饰静态方法 优点.缺点及优化 其他说明 基础 Java 在虚拟机层面提供了 synchronized 关键字供开发者快速实现互斥同步的重量级锁来保障线程安全. synchronized 关键字可用于两种场景: 修饰方法. 持有一个对象,并执行一个代码块. 而根据加锁的对象不同,又分为两种情况: 对象锁 类对象锁 以下代码示例是 synchron

  • 实例讲解Java中的synchronized

    一.使用场景 在负责后台开发的时候,很多时候都是提供接口给前端开发人员去调用,会遇到这样的场景: 需要提供一个领奖接口,每个用户名只能领取一次,我们可以将成功领取的用户在数据库用个标记保存起来.如果这个用户再来领取的时候,查询数据库看该用户是否领取过. 但是问题来了,假设用户手速很快,极短时间内点了两次领奖按钮(前端没有进行控制,我们也不能依赖前端去控制).那么可能掉了两次领奖接口,而且有可能第二次调用的时候查询数据库的时候,第一次领奖还没有执行完成更新领奖标记. 这种场景就可以使用到synch

  • 超详细讲解Java线程池

    目录 池化技术 池化思想介绍 池化技术的应用 如何设计一个线程池 Java线程池解析 ThreadPoolExecutor使用介绍 内置线程池使用 ThreadPoolExecutor解析 整体设计 线程池生命周期 任务管理解析 woker对象 Java线程池实践建议 不建议使用Exectuors 线程池大小设置 线程池监控 带着问题阅读 1.什么是池化,池化能带来什么好处 2.如何设计一个资源池 3.Java的线程池如何使用,Java提供了哪些内置线程池 4.线程池使用有哪些注意事项 池化技术

  • 实例解析Java中的synchronized关键字与线程安全问题

    首先来回顾一下synchronized的基本使用: synchronized代码块,被修饰的代码成为同步语句块,其作用的范围是调用这个代码块的对象,我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步.这叫减小锁的粒度,使代码更大程度的并发. synchronized方法,被修饰的方法成为同步方法,其作用范围是整个方法,作用对象是调用这个方法的对象. synchronized静态方法,修饰一个static静态方法,其作用范围是整个

  • Java中使用synchronized关键字实现简单同步操作示例

    简单记录下java中synchronized关键字的使用方法. 在介绍之前需要明确下java中的每一个类的对象实例都有且只有一个锁(lock)和之相关联,synchronized关键字只作用于该锁,即可以认为synchronized只对java类的对象实例起作用. synchronized修饰函数 复制代码 代码如下: public synchronized aMethod(){ } 这就是最常用的情景,那么这个同步方法的用途是啥,为了方便就记作aMethod方法. 1.synchronized

  • 详解java中的synchronized关键字

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块.

  • 深入理解java中的synchronized关键字

    synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A,没有的话,直接运行它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法.如: 复制代码 代码如下: publ

  • Java线程中的关键字和方法示例详解

    目录 一.volatile关键字 1,volatile能保证内存可见性 2,编译器优化问题 二.wait和notify 1,wait()方法 2,notify()方法 3,notifyAll()方法 一.volatile关键字 1,volatile 能保证内存可见性 代码在写入 volatile 修饰的变量的时候 改变线程工作内存中volatile变量副本的值 将改变后的副本的值从工作内存刷新到主内存 代码在读取 volatile 修饰的变量的时候 从主内存中读取volatile变量的最新值到线

随机推荐