JavaSE 多线程

来源:互联网 发布:root软件哪个好 编辑:程序博客网 时间:2024/06/07 16:15

1 线程概述

1.1 java 是为数不多的支持多线程的语言。

1.2 几乎所有的操作系统都支持进程的概念,所有运行中的任务通常对应一个进程。当一个程序进入内存运行时,即变成了一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。

1.3 一般而言,进程包含如下三个特征:

1.3.1 独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。

1.3.2 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。

1.3.3 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

1.4 操作系统可以执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。

1.5 一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但至少要包含一个线程。



2 多线程的优势

1.1 进程之间不能共享内存,但线程之间共享内存非常容易。

1.2 系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高

1.3 Java语言内置了多线程功能支持,而不是单纯作为底层操作系统的调度方式,从而简化了Java的多线程编程。


3 线程的创建和启动

3.1如何创建一个线程呢?

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

步骤:

3.1.1 定义一个类继承Thread 类

3.1.2 复写Thread类中的run方法,将线程要运行的代码封装到run方法当中。

3.1.3 并创建Thread的子类对象创建线程

3.1.4 调用start方法,开启线程并调用run方法执行。

3.2 run方法中定义的就是线程要运行的任务代码,开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法。

    将运行的代码定义在run方法中即可。

3.3  如何区分现在正在运行的线程是哪个?

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

因为线程在创建时就已经别赋予了默认的名字,所以无法反应当前执行的线程的名字

此时需要用到Thread.currentThread().getName()获取当前正在执行的线程的名字

public class Demo extends Thread{
private String name;
Demo(String name){
//super(name);
this.name=name;
}
public void run(){
for (int x = 1; x <=10; x++) {
for (int y = 0; y < 999999999; y++) {}//刻意写个循环让程序运行慢点
System.out.println(name+"------"+x+"----name="+getName()+"---                                                       "+Thread.currentThread().getName());
}
}

public static void main(String[] args) {
Demo d1=new Demo("旺财");
Demo d2=new Demo("小明");
d1.start();
d2.start();
for (int i = 0; i < 20; i++) {
System.out.println("-------"+Thread.currentThread().getName());
}
}

}

3.4 多线程的JVM内存状态

三条线程相互独立,互不冲突,若有线程发生异常其他线程不受影响。



3.5 线程的几种状态



4 创建线程的第二种方式

4.1.1 定义类实现Runnable接口

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

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

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

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

4.1.4 调用线程对象的start方法开启线程

public class ThreadDemo {
public static void main(String[] args) {
Demo2 d1=new Demo2();
Thread t1=new Thread(d1);
Thread t2=new Thread(d1);

t1.start();
t2.start();
// Demo2 d2=new Demo2();
// d1.start();
// d2.start();
}
}


class Demo2 implements Runnable{
public void run(){
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 9999999; j++) {}
System.out.println(Thread.currentThread().getName()+",,,,,"+i);
}
}
}

实现Runnable接口的好处

 1,将线程的任务从线程的子类中分离出来,进行了单独的封装。

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

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


4.2 买票示例

4.2.1 加上static后可以实现数据共享,但是不建议这么做,假设有两个一百张票分别由四个窗口买票,加上static就不能满足要求了。


4.2.2 class Ticket implements Runnable{//extends Thread{
private  int num=100;
public void run(){
while(true){
if (num>0) {
System.out.println(Thread.currentThread().getName()+"----sale----"+num--);
}
}
}
}
class TicketDemo{
public static void main(String[] args) {

Ticket t=new Ticket();//创建一个线程任务
//Ticket tt=new Ticket();

Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);

t1.start();
t2.start();
t3.start();
t4.start();

/*
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();

t1.start();
t2.start();
t3.start();
t4.start();
*/
}
}

4.3 上述代码中可能出现的问题



class Ticket implements Runnable{//extends Thread{
private  int num=100;
public void run(){
while(true){
if (num>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName()+"----sale..."+num--);
}
}
}
}
class TicketDemo{
public static void main(String[] args) {

Ticket t=new Ticket();//创建一个线程任务
//Ticket tt=new Ticket();

Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);

t1.start();
t2.start();
t3.start();
t4.start();

/*
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();

t1.start();
t2.start();
t3.start();
t4.start();
*/
}
}

出现了0票,-1票或者-2

线程安全问题产生的原因:

1,多个线程在操作共享的数据

2,操作共享数据的线程代码有多条

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算就会导致线程安全问题的产生。


如何解决?

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

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


同步代码块的格式:

synchronized(对象){

需要被同步的代码;

}

class Ticket implements Runnable{//extends Thread{
private  int num=100;
Object obj=new Object();
public void run(){
while(true){
synchronized(obj){
if (num>0) {
try {Thread.sleep(10);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+"----sale...."+num--);
}
}
}

}
}
class TicketDemo{
public static void main(String[] args) {

Ticket t=new Ticket();//创建一个线程任务
//Ticket tt=new Ticket();

Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);

t1.start();
t2.start();
t3.start();
t4.start();

/*
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();

t1.start();
t2.start();
t3.start();
t4.start();
*/
}
}

4.4 同步代码块解决安全问题的原理


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

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


4.5 同步的前提:

   同步中必须有多个线程并且使用同一个锁。








0 0
原创粉丝点击