02 如何创建线程 线程并发与synchornized

所有程序运行结果 请自行得出

创建线程方式一:继承Thread类

步骤:

1,定义一个类继承Thread类。

2,覆盖Thread类中的run方法。

3,直接创建Thread的子类对象创建线程。

4,调用start方法开启线程并调用线程的任务run方法执行。

 1 /*
 2  * 需求:我们要实现多线程的程序。
 3  * 如何实现呢?
 4  *     由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。
 5  *     而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
 6  *     Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
 7  *     但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
 8  *     由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
 9  *     然后提供一些类供我们使用。我们就可以实现多线程程序了。
10
11  * 那么Java提供的类是什么呢?
12  *         Thread
13  *         通过查看API,我们知道了有2中方式实现多线程程序。
14
15  * 方式1:继承Thread类。
16  * 步骤
17  *     A:自定义类MyThread继承Thread类。
18  *     B:MyThread类里面重写run()?
19  *     C:创建对象 new
20  *     D:启动线程 start
21  */
22
23
24 /*
25  * 该类要重写run()方法,为什么呢?
26  * 不是类中的所有代码都需要被线程执行的。
27  * 而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
28  */
29
30 public class MyThread extends Thread {
31     @Override
32     public void run() {
33         // 自己写代码
34         // 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环
35         for (int x = 0; x < 200; x++) {
36             System.out.println(x);
37         }
38     }
39 }

若执行以下代码:

MyThread my = new MyThread();
my.run();
my.run();

观察结果:程序是顺序执行的,并没有交替执行的现象的,为什么?

答:调用run()方法是单线程的。

因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果

要想看到多线程的效果,就必须说说另一个方法:start()

MyThread my = new MyThread();

my.start();

面试题:run()和start()的区别?

run():仅仅是封装被线程执行的代码,直接调用是普通方法

start():首先启动了线程,然后再由jvm去调用该线程的run()方法

由于只start()了一次,观察不到想要的结果,那么就会想出如下的代码:

MyThread my = new MyThread();

my.start();

my.start();

运行后会发现抛出错误: IllegalThreadStateException:非法的线程状态异常

为什么呢?

因为start()2次相当于是my线程被调用了两次,而不是两个线程启动。

一个线程只能调用一次

要想观察到多线程的效果,正确的代码是:

1 MyThread my1 = new MyThread();
2 MyThread my2 = new MyThread();
3 my1.start();
4 my2.start();

运行后就能够看见效果了。

1.1线程名字

通过运行结果我们可以发现:我们并不知道谁是谁? 即哪个线程在执行

如何获取线程的名称呢?  getName()方法

 1 public class MyThread extends Thread {
 2     public void run() {
 3        for (int x = 0; x < 100; x++) {
 4            System.out.println(getName() + ":" + x);
 5        }
 6     }
 7 }
 8
 9 MyThread my1 = new MyThread();
10 MyThread my2 = new MyThread();
11 my1.start();
12 my2.start();

发现:可以通过Thread的getName获取线程的名称 Thread-编号(从0开始)

名称为什么是:Thread-? 编号

下面看一下Thread的代码:

 1 class MyThread extends Thread {
 2     public MyThread() {
 3         super();
 4     }
 5 }
 6
 7 class Thread {
 8     private char name[];
 9
10     public Thread() {
11         init(null, null, "Thread-" + nextThreadNum(), 0);
12     }
13
14     private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
15         init(g, target, name, stackSize, null);
16     }
17
18      private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {
19         //大部分代码被省略了
20         this.name = name.toCharArray();
21     }
22
23     public final void setName(String name) {
24         this.name = name.toCharArray();
25     }
26
27     private static int threadInitNumber; //0,1,2
28     private static synchronized int nextThreadNum() {
29         return threadInitNumber++; //return 0,1
30     }
31
32     public final String getName() {
33         return String.valueOf(name);
34     }
35 }

由原码可知:Thread通过一个字符数组name表示线程名称,用threadInitNumber变量来记录线程的编号

每当一个线程调用时,threadInitNumber++以达到计数的目的,然后 "Thread-" + threadInitNumber转成字符数组给name

调用getName()是将name转换为string,这就是我们看到的效果。

但默认的方法对我们来说往往没有意义。但我们可以通过setName(String name)来设置我们想要的名字

 1 我们还可以通过有参数的构造方法来命名线程
 2 public class MyThread extends Thread {
 3     public MyThread() {
 4     }
 5
 6     public MyThread(String name){
 7         super(name);
 8     }
 9
10     @Override
11     public void run() {
12         for (int x = 0; x < 100; x++) {
13             System.out.println(getName() + ":" + x);
14         }
15     }
16 }
17 //带参构造方法给线程起名字
18 MyThread my1 = new MyThread("林青霞");
19 MyThread my2 = new MyThread("刘意");
20 my1.start();
21 my2.start();

主线程的名字就是main 我们如何获取呢?

遇到这种情况,Thread类提供了一个很好玩的方法: public static Thread currentThread():返回当前正在执行的线程对象

在main方法中使用下面语句就可以得到多线程的名称 System.out.println(Thread.currentThread().getName());

1.2线程调度优先级

假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,

线程只有得到 CPU时间片,也就是使用权,才可以执行指令。

那么Java是如何对线程进行调用的呢?

线程有两种调度模型:

分时调度模型     所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

抢占式调度模型   优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

Java使用的是抢占式调度模型:

static int MAX_PRIORITY 线程可以具有的最高优先级    10

static int MIN_PRIORITY  线程可以具有的最低优先级   1

static int NORM_PRIORITY 分配给线程的默认优先级     5

public final void setPriority(int newPriority)更改线程的优先级

public final int getPriority()返回线程的优先级

* 注意:

线程默认优先级是NORM_PRIORITY = 5

线程优先级的范围是:1-10

线程优先级高仅仅表示线程获取的 CPU时间片的几率高(并非高优先级的线程一定每次都优先),

但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。

 1 public class ThreadPriority extends Thread {
 2
 3     public void run() {
 4
 5        for (int x = 0; x < 100; x++) {
 6            System.out.println(getName() + ":" + x);
 7        }
 8     }
 9 }
10
11 ThreadPriority tp1 = new ThreadPriority();
12 ThreadPriority tp2 = new ThreadPriority();
13 ThreadPriority tp3 = new ThreadPriority();
14
15
16 tp1.setName("东方不败");
17 tp2.setName("岳不群");
18 tp3.setName("林平之");
19
20
21 // 获取默认优先级
22 System.out.println(tp1.getPriority());
23 System.out.println(tp2.getPriority());
24 System.out.println(tp3.getPriority());
25
28 //设置线程优先级
29 tp1.setPriority(10);
30 tp2.setPriority(1);
31
34 tp1.start();
35 tp2.start();
36 tp3.start();
37 

如果设置线程优先级: tp1.setPriority(100000);

报错:

IllegalArgumentException:非法参数异常。

抛出的异常表明向方法传递了一个不合法或不正确的参数。

1.3 sleep

线程休眠  public static void sleep(long millis)

Thread.sleep(1000); //1秒

  在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),

  此操作受到系统计时器和调度程序精度和准确性的影响。

  该线程不丢失任何监视器的所属权时间到后该线程继续执行 即不会执行的权利 时间到后继续往下走

我们可以通过以下代码感受:

 1 public class ThreadSleep extends Thread {
 2     @Override
 3     public void run() {
 4         for (int x = 0; x < 100; x++) {
 5             System.out.println(getName() + ":" + x + ",日期:" + new Date());
 6             try {
 7                 Thread.sleep(1000);
 8             } catch (InterruptedException e) {
 9                 e.printStackTrace();
10             }
11         }
12     }
13 }
14 public class ThreadSleepDemo {
15     public static void main(String[] args) {
16         ThreadSleep ts1 = new ThreadSleep();
17         ThreadSleep ts2 = new ThreadSleep();
18         ThreadSleep ts3 = new ThreadSleep();
19
20         ts1.setName("林青霞");
21         ts2.setName("林志玲");
22         ts3.setName("林志颖");
23
24         ts1.start();
25         ts2.start();
26         ts3.start();
27     }
28 }

创建线程的第二种方式:实现Runnable接口

1,定义类实现Runnable接口。

2,覆盖接口中的run方法,将线程的任务代码封装到run方法中。

3,通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。

为什么?

为线程的任务都封装在Runnable接口子类对象的run方法中。

所以要在线程对象创建时就必须明确要运行的任务。

4,调用线程对象的start方法开启线程。

实现Runnable接口的好处:

1,将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务的封装成对象。

2,避免了java单继承的局限性。

所以,创建线程的第二种方式较为常用。

实例

 1 /*
 2  * 方式2:实现Runnable接口
 3  * 步骤:
 4  *         A:自定义类MyRunnable实现Runnable接口
 5  *         B:重写run()方法
 6  *         C:创建MyRunnable类的对象
 7  *         D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
 8  */
 9
10 public class MyRunnable implements Runnable {
11
12     @Override
13     public void run() {
14         for (int x = 0; x < 100; x++) {
15             // 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
16             System.out.println(Thread.currentThread().getName() + ":" + x);
17         }
18     }
19
20 }
21
22 使用:
23 MyRunnable my = new MyRunnable();
24 // 创建Thread类的对象,并把C步骤的对象作为构造参数传递 Thread(Runnable target)
25 Thread t1 = new Thread(my);
26 Thread t2 = new Thread(my);
27 t1.setName("林青霞");
28 t2.setName("刘意");
29 t1.start();
30 t2.start();
31
32 MyRunnable my = new MyRunnable();
33 // Thread(Runnable target, String name)
34 Thread t1 = new Thread(my, "林青霞");
35 Thread t2 = new Thread(my, "刘意");
36 t1.start();
37 t2.start();

2.1比较

2.2 实现Runnable的最大好处就是可以实现共享同一资源

案例2:经典的买票问题

某电影院目前正在上映贺岁大片(红高粱,少林寺传奇藏经阁),

共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

先用Thread实现

public class SellTicket extends Thread {

    // 定义100张票
    // private int tickets = 100;
    // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
    private static int tickets = 100;

    public void run() {

       // 定义100张票
       // 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
       // int tickets = 100;
       // 是为了模拟一直有票
       while (true) {
           if (tickets > 0) {
              System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
           }
       }
    }
}

public class SellTicketDemo {

    public static void main(String[] args) {

       // 创建三个线程对象
       SellTicket st1 = new SellTicket();
       SellTicket st2 = new SellTicket();
       SellTicket st3 = new SellTicket();

       // 给线程对象起名字
       st1.setName("窗口1");
       st2.setName("窗口2");
       st3.setName("窗口3");

       // 启动线程
       st1.start();
       st2.start();
       st3.start();
    }
}

现在用Runable接口:

 1 public class SellTicket implements Runnable {
 2     // 定义100张票
 3     private int tickets = 100;
 4     public void run() {
 5        while (true) {
 6            if (tickets > 0) {
 7
 8               try{Thread.sleep(100)}catch(Exception e){}
 9
10               System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
11            }
12        }
13     }
14 }
new Thread(new SellTicket()).start();
new Thread(new SellTicket()).start();
new Thread(new SellTicket()).start();

我们每次卖票都延迟100毫秒,此时你就会出现问题

原因就是因为多个线程并发导致了共享数据的安全问题

如何解决线程安全问题呢?

要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)

A:是否是多线程环境

B:是否有共享数据

C:是否有多条语句操作共享数据

我们来回想一下我们的程序有没有上面的问题呢?

A:是否是多线程环境  是

B:是否有共享数据   是

C:是否有多条语句操作共享数据   是

由此可见我们的程序出现问题是正常的,因为它满足出问题的条件。

接下来才是我们要想想如何解决问题呢?

A和B的问题我们改变不了,我们只能想办法去把C改变一下。

Solution:

 基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。

 通常来说,是在访问临界资源前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。

解决思就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

Java提供的解决方案

思想:

把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。

问题是我们不知道怎么包啊?Java给我们提供了:同步机制。

解决思就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,

其他线程时不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

在java中,用同步代码块就可以解决这个问题。

同步代码块的格式:

synchronized(对象)

{

    需要被同步的代码

}

A:对象是什么呢?

我们可以随便创建一个对象试试。(对象其实可以是任意类型的,但必须是同一个对象)

synchronized(new Object()){}  No

synchronized(object){}         Yes  可以使用 class文件

B:需要同步的代码是哪些呢?

把多条语句操作共享数据的代码的部分给包起来

注意:

同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

多个线程必须是同一把锁。

同步的好处:解决了线程的安全问题。

同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。

同步的前提:同步中必须有多个线程并使用同一个锁

l  synchronized 代码块

 synchronized(...){
    .....
  }
 卖票问题的解决:
 1 public class ThreadDemo03 {
 2     public static void main(String[] args)
 3     {
 4         Ticket t = new Ticket();//创建一个线程任务对象  此时只有一个Ticket对象  且只能有一个
 5
 6         Thread t1 = new Thread(t);
 7         Thread t2 = new Thread(t);
 8         Thread t3 = new Thread(t);
 9         Thread t4 = new Thread(t);
10
11         t1.start();
12         t2.start();
13         t3.start();
14         t4.start();
15     }
16 }
17
18 class Ticket implements Runnable{
19     private  int num = 100;    //共享资源 必须放在这里 而不能在run()中
20     Object obj = new Object();//对象锁只能有一个
21
22     public void run()
23     {
24         while(true)
25         {
26             synchronized(obj)
27             {
28                 if(num>0)
29                 {
30                     try{Thread.sleep(1000);}catch (InterruptedException e){}
31                     System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
32                 }
33             }
34         }
35     }
36 }

l  synchronized 方法:

public synchronized type function (... ...); 
 1 private static synchronized void sellTicket() {
 2         if (tickets > 0) {
 3         try {
 4                 Thread.sleep(100);
 5         } catch (InterruptedException e) {
 6                 e.printStackTrace();
 7         }
 8         System.out.println(Thread.currentThread().getName()
 9                     + "正在出售第" + (tickets--) + "张票 ");
10         }
11 }

l  当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

对象的synchronized方法不能进入了,但它的其他非synchronized方法还是可以访问的

 1 public class TT implements Runnable {
 2
 3      int b = 100;
 4
 5      public synchronized void m1(){
 6             b = 10000;
 7             try {
 8                 Thread. sleep(5000);
 9                 System. out.println( "m1----b="+ b);
10            } catch (InterruptedException e) {
11                 e.printStackTrace();
12            }
13      }
14
15      public void m2(){
16            System. out.println( "m2====="+ b);
17      }
18
19      @Override
20      public void run() {
21            m1();
22      }
23
24      public static void main(String[] args) {
25            TT tt = new TT();
26            Thread t = new Thread(tt);
27            t.start();
28
29             try {
30                 Thread. sleep(1000);
31            } catch (InterruptedException e) {
32                 e.printStackTrace();
33            }
34
35            tt.m2();
36      }
37
38 }

锁对象是谁?

* A:同步代码块的锁对象是谁呢?

*     任意对象。

* B:同步方法的格式及锁对象问题?

*     把同步关键字加在方法上。

*     同步方法是谁呢?

*         this

* C:静态方法及锁对象问题?

*     静态方法的锁对象是谁呢?

*     类的字节码文件对象。(反射会讲)

线程安全的类

StringBuffer sb = new StringBuffer();

Vector<String> v = new Vector<String>();

Hashtable<String, String> h = new Hashtable<String, String>();

Vector是线程安全的时候才去考虑使用的,但是即使要安全,我也不用你

那么到底用谁呢?public static <T> List<T> synchronizedList(List<T> list)

List<String> list1 = new ArrayList<String>();// 线程不安全

List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全

synchornized

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。

如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

  1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

  2)线程执行发生异常,此时JVM会让线程自动释放锁。

原理示意图

jdk1.5还提供了lock锁 后面再说

时间: 2024-10-08 19:01:37

02 如何创建线程 线程并发与synchornized的相关文章

UNIX网络编程卷1 服务器程序设计范式6 并发服务器,为每个客户请求创建一个线程

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.为每个客户请求创建一个线程,以取代为每个客户派生一个子进程 /* include serv06 */ #include "unpthread.h" int main(int argc, char **argv) { int listenfd, connfd; void sig_int(int); void *doit(void *); pthread_t tid; sockl

C#高级知识点概要(2) - 线程和并发

原文地址:http://www.cnblogs.com/Leo_wl/p/4192935.html 我也想过跳过C#高级知识点概要直接讲MVC,但经过前思后想,还是觉得有必要讲的.我希望通过自己的经验给大家一些指引,带着大家一起走上ASP.NET MVC大牛之路,少走弯路.同时也希望能和大家一起交流,这样也能发现我自己的不足,对我自己的帮助也是非常大的. 建议大家对C#撑握的不错的时候,可以去看一些开源项目.走技术这条路,就要耐得住寂寞(群里双休日说要让群主找妹子进群的人必须反思),练好内功.不

[ASP.NET MVC 大牛之路]03 - C#高级知识点概要(2) - 线程和并发

我也想过跳过C#高级知识点概要直接讲MVC,但经过前思后想,还是觉得有必要讲的.我希望通过自己的经验给大家一些指引,带着大家一起走上ASP.NET MVC大牛之路,少走弯路.同时也希望能和大家一起交流,这样也能发现我自己的不足,对我自己的帮助也是非常大的. 建议大家对C#撑握的不错的时候,可以去看一些开源项目.走技术这条路,就要耐得住寂寞(群里双休日说要让群主找妹子进群的人必须反思),练好内功.不撑握C#高级知识点,别想看懂优秀的开源项目,更别指望吸收其编程思想:你的水平,随时可以被一个实习生代

.Net组件程序设计之线程、并发管理(一)

.Net组件程序设计之线程.并发管理(一) 1.线程 线程 线程的创建 线程的阻塞 线程挂起 线程睡眠 加入线程 线程中止 现在几乎所有的应用程序都是多线程的,给用户看来就是一个应用程序界面(应用程序线程),不管什么操作都不会导致界面出现响应慢的情况,这些都是多线程的功劳,有了多线程,可以让应用程序尽最大可能的处理更多的操作,调动很多线程来并行处理请求,这样会使得应用程序有更大的系统吞吐量. 1.线程 1.1线程 线程是什么呢?线程就是进程中的一条执行路径,每个应用程序至少在一个线程上运行.在本

进程、线程的并发

进程.线程的并发 本文是自己学习经验总结,有不正确的地方,请批评指正. 总结一下这一段时间来,有关网络编程的学习.我是从csapp的最后章节的Tiny HTTP服务器开始,以它为基础,改用不同的方式实现并发,包括进程.线程.线程池.I/O多路复用.所有代码见地址:https://github.com/xibaohe/tiny_server 一.基于进程.线程的并发 关于进程和线程的网络编程模型,在UNP卷1的第30章,有详细的介绍.我这里,在Tiny基础上,实现了以下几种: tiny_proce

C#之线程和并发

建议大家对C#撑握的不错的时候,可以去看一些开源项目.走技术这条路,就要耐得住寂寞(群里双休日说要让群主找妹子进群的人必须反思),练好内功.不撑握C#高级知识点,别想看懂优秀的开源项目,更别指望吸收其编程思想:你的水平,随时可以被一个实习生代替!切记不能浮躁! 本文讲线程和并发,这块知识点太多太多了,不可能用一篇文章写的面面具到(本身主题就是C#高级知识概要嘛),我所了解的也有限.但对于Web开发,我想本文的知识点应该足够,如果后面有遇到本文没讲的,后面再补充吧. 本文目录: 线程的简单使用 并

Java线程与并发编程实践----并发工具类与Executor框架

java5之前,我们使用诸如synchronized,wait(),notify()方法对线程的操作属于对 底层线程的操作,这样会出现很多的问题: 低级的并发原语,比如synchronized,wait(),notify()经常难以正确使用.误用会导致 竞态条件,线程饿死,死锁等风险. 泰国依赖synchronized会影响程序性能以及程序的可扩展性 开发者经常需要高级线程结构,如线程池,信号量.java对底层线程的操作不包含这些结. 为解决这些问题,java5引入并发工具类,该工具类主要有下面

信号量 也是同步锁,可用来控制线程的并发数

# 信号量 也是同步锁,可用来控制线程的并发数 import threading, time class MyThread(threading.Thread): def run(self): if semaphore.acquire(): # 同时运行五个线程,acquire()放一个进程进去计数器-1 print(self.name) # 计数器为0时阻塞线程至同步锁定状态,等待release() time.sleep(3) semaphore.release() # release()运行一

Java多线程01(Thread类、线程创建、线程池)

Java多线程(Thread类.线程创建.线程池) 第一章 多线程 1.1 多线程介绍 1.1.1 基本概念 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程.一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序. 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 1.1.2 单线程程序 - 从入口m