进程:正在进行的程序。其实就是一个应用程序运行时的内存分配空间。

线程:进程中一个程序执行控制单元,一条执行路径。进程负责的事应用程序的空间的标识,线程负责的事应用程序的执行顺序。

进程和线程的关系:一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、和变量。

JVM启动时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。

当产生垃圾时,收垃圾的动作,是不需要主线程来完成的,应为这样,会出现主线程中的代码执行停止,去运行垃圾会收器代码,效率较低,所以由一个单独的线程来负责垃圾回收。

一、创建多线程程序的两种方法:Thread类和Runnable接口

  (一)通过继承Thread类来完成

    1、步骤:

      (1)定义类继承Thread类;

      (2)复写run方法,将要让线程运行的代码都储存到run方法中

      (3)通过创建Thread类的子类对象,创建线程对象

      (4)调用线程的start方法,开启线程,并自动执行run方法,注意:start()方法会执行两条命令,1、开启线程,2、执行run方法

    2、线程的状态:

      (1)被创建:start();

      (2)运行:具备执行资格,同时具备执行权。

      (3)冻结:sleep(time),wait()---notify()唤醒;线程释放执行权,同时释放执行资格;

      (4)零时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权

      (5)消亡:stop()

  (二)通过实现Runnable接口来完成(如果一个类已经继承了其他类,就不能继承Thread类了,Java的单继承局限性。于是只能对该类进行额外的功能扩展)

    1、步骤:

      (1)定义类实现Runnbale接口;

      (2)覆盖接口中的run方法(封装线程要执行的代码);

      (3)通过Thread类创建线程对象;

      (4)将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数;(明确线程要执行的代码run方法)

      (5)调用Thread对象的start()方法,开启线程,并执行Runnable接口的子类对象中的run()方法。

    2、Runnable接口

      (1)避免了Java单继承的局限性

      (2)Thread类描述时,内部定义的run方法,也来自于Runnable接口

      (3)Runnable接口将线程要执行的任务封装成了对象

  (三)案例

    1、买票

      (1)通过继承Thread类

 class Ticket extends Thread
{
private static int num = 100;//出售票的总数,如果不设置静态,每创建一个对象,就会有属于自己的100张票,最终会出售400张票,所以设置为静态,让他成为所有对象共享的数据。
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 t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket(); t1.start();
t2.start();
t3.start();
t4.start();
}
}

      (2)通过实现Runnable接口

 class Ticket implements Runnable
{
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();
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();
}
}

二、多线程的安全问题

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

  1、出现安全问题的两个前提

    (1)多个线程在操作共享数据

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

  2、解决方案

     只要让某一线程在执行操作共享数据的多条代码时,让其他程序不能执行数据的操作就可以了。即同步

三、同步

  1、定义同步的两个前提

    (1)必须要有两个或两个以上的线程,才需要同步

    (2)多个线程必须保证使用的是同一个锁

  2、同步的两种表现形式

    (1)同步代码块:

        格式:

synchronized(对象)//可以是任意对象,该对象其实就是锁
{
//需要被同步的代码
}

        案例:

          a、上边买票的案例优化

 class Ticket implements Runnable
{
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();
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();
}
}

        b、一张银行卡,两个人每人每次到银行存100,存三次

 class Bank
{
private int sum;
private Object obj = new Object();
public void add(int num)
{
synchronized(obj)//注意:不能再此处直接new Object(),这样的话,每次创建一个对象,就会new一个新的Object,用的就不是同一个锁了,
{
sum = sum+num;
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...sum="+sum);
}
}
} class Cus implements Runnable
{
private Bank b = new Bank();
public void run()
{
for (int x=3; x>0; x--) {
b.add(100);
}
}
} class BankDemo
{
public static void main(String[] args)
{
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c); t1.start();
t2.start(); }
}

    (2)同步函数:就是将同步关键字定义在函数上,让函数具备同步性。

        同步函数锁使用的锁就是this

        当同步函数被static修饰时,静态函数的调用不需要对象,但是静态函数所属类的字节码文件在加载进内存时,这个字节码文件就被封装成了对象。所以此时同步函数所使用的锁就是该类的字节码文件对象。(类名.class)

        案例:上边银行存款的例子、

 class Bank
{
private int sum;
private Object obj = new Object();
public synchronized void add(int num)
{
//synchronized(obj)//注意:不能再此处直接new Object(),这样的话,每次创建一个对象,就会new一个新的Object,用的就不是同一个锁了,
//{
sum = sum+num;
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...sum="+sum);
//}
}
} class Cus implements Runnable
{
private Bank b = new Bank();
public void run()
{
for (int x=3; x>0; x--) {
b.add(100);
}
}
} class BankDemo
{
public static void main(String[] args)
{
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c); t1.start();
t2.start(); }
}

  2、同步代码块和同步函数的区别

    (1)同步代码块使用的锁可以是任意对象。

        同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

  3、用同步代码块还是同步函数

    在一个类中只有一个同步时,可以使用同步函数,但是如果有多个同步,就必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。

  4、使用同步的好处和弊端

    (1)好处:解决了线程的安全问题

    (2)弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

        死锁:常见情景之一:同步的嵌套

        

 class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
} public void run()
{ if(flag)
{
while(true)
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"..if locka....");
synchronized(MyLock.lockb) { System.out.println(Thread.currentThread().getName()+"..if lockb....");
}
}
}
else
{
while(true)
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"..else lockb....");
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"..else locka....");
}
}
} } } class MyLock
{
public static final Object locka = new Object();
public static final Object lockb = new Object();
} class DeadLockTest
{
public static void main(String[] args)
{
Test a = new Test(true);
Test b = new Test(false); Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}

四、单例模式涉及的多线程问题

  1、饿汉式

 class Single
{
private static finally Single s = new Single();//静态方法访问的内容必须是静态的。
private Single(){}
public static Single getInstance()//因为单例不能在其他类中创建单例类对象,所以getInstance方法必须是静态的,可以直接被Single类调用。
{
return s;
}
}

  2、懒汉式(单例模式的延迟加载形式)

    当多线程访问懒汉式时,懒汉式的getInstance方法中对共享数据s进行了多条语句的操作,所以容易出现线程安全问题。为了解决该问题,加入同步机制,但是每次调用懒汉式对象,都会判断synchronized锁,降低了效率。于是通过双重判断的形式解决效率问题。

 class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s==null)
{
synchronized(Single.class)//此处的锁是懒汉式类字节码文件对象
{
if(s==null)
s = new Single();
}
}
return s;
}
}

五、多线程

  1、线程间通讯

    多个线程在操作同一个资源,但是操作的动作却不一样。

      思路:(1)将资源封装成对象;

         (2)将不同线程执行的任务(其实就是run方法)也封装在不同的对象中。

    多生产者多消费者问题

 /*
* 资源对象
*/
class Resource
{
private String name;
private int count =1;
private Boolean flag = false;
public synchronized void set(String name)
{
while(flag)
{
try{this.wait();}catch(InterruptedException e) {}
}
this.name = name+this.count;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
count++;
flag = true;
notifyAll();//t0,t1的锁是pro对象,t2,t3的锁是con对象,这里的notifyAll()只是唤醒以Pro对象为锁的线程
}
public synchronized void out()
{
while(!flag)
{
try{this.wait();}catch(InterruptedException e) {}
}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
notifyAll();
}
} /*
* 生产者对象
*/
class Producer implements Runnable
{
private Resource r;
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("烤鸭");
}
}
} class Consumer implements Runnable
{
private Resource r;
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
} /*
* 消费者对象
*/
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}

    相关知识:

      等待唤醒机制

        wait();  将同步中的线程处于冻结状态。释放了执行权,释放了执行资格。同时将线程对象存储到线程池中。

        notify();  唤醒线程池中某一个等待的线程。如果A锁上的线程被wait了,那么此时该线程就处于A锁的线程池中,只能由A锁的notify()唤醒。

        notifyAll(); 唤醒线程池中所有线程。

        以上三个方法都被定义在Object类中,是因为这些方法存在于同步中,调用时都必须要标识所属的同步的锁,而锁可以是任意对象,所以任意对象都可以使用的方法一定定义在Object类中。

      wait和sleep的区别:
        (1)wait可以指定等待时间也可以不指定时。不指定时间,只能由对应的notify()或者notifyAll()来唤醒

            sleep必须指定时间,时间到了就自动从冻结状态转成运行状态(临时阻塞状态)

        (2)wait线程会释放执行权,而且线程会释放锁

            sleep线程会释放执行权,但不是不释放锁

      线程的停止:

        (1)通过stop方法停止,但是该方式已过时

        (2)让线程运行的代码结束,也就是结束run方法。一般run方法里一定有循环,所以只要结束循环即可。a、定义循环的结束标记;b、如果线程处于冻结状态,是不能督导标记的,这时就需要通过Thread类的interrupt方法,将其冻结状态强制清除,让线程恢复到具备执行资格的状态,这时该线程就可以读到结束标记,并结束线程。

    java.lang.Thread类相关方法:

        interrupt()        中断线程,其实是将线程从冻结状态强制恢复到运行状态中,让线程具备CPU的之赐你个资格。

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

        getPriority();       返回线程的优先级

        tiString()           返回该线程的字符串表示形式,包括线程名称、优先级和线程组

        Thread.yield()       暂停当前正在执行的线程对象,并执行其他线程

        setDaemon(true)     将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。

        join()           临时加入一个线程时可以调用join()方法

六、面试题

  1、以下代码是否有错误?

 class Test implements Runnable
{
public void run(Thread t)
{}
}

    解析:第一行有错误,class应该被abstract修饰,public void run(Thread t)方法没有覆盖Runnable接口的run方法,是函数的重载,所以Test类中有抽象方法,是抽象类。

  2、以下代码是否有错误,如果没有,运行结果是什么?

class ThreadTest {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run()
{
System.out.println("runnable run");
}
})
{
public void run() {
System.out.println("subThread run");
}
}.start();
}
}

    解析:没有错误,结果是subThread run,

JavaSE基础---多线程的更多相关文章

  1. 基础1 JavaSe基础

    JavaSe基础 1. 九种基本数据类型的大小,以及他们的封装类 boolean 无明确指定 Boolean char 16bits Character byte 8bits Byte short 1 ...

  2. java学习之路之javaSE基础1

    <h2>java学习之路之javaSE基础1</h2> <div> ###01.01_计算机基础知识(计算机概述)(了解)* A:什么是计算机?计算机在生活中的应用 ...

  3. javaSE基础07

    javaSE基础07 一.static静态修饰符 用了static修饰的变量就会变成共享的属性,只会初始化一次,在内存中只存在一个,并且每个对象都可以访问,存放在方法区(数据共享区) 1.1 stat ...

  4. javaSE基础06

    javaSE基础06 一.匿名对象 没有名字的对象,叫做匿名对象. 1.2匿名对象的使用注意点: 1.我们一般不会用匿名对象给属性赋值的,无法获取属性值(现阶段只能设置和拿到一个属性值.只能调用一次方 ...

  5. javaSE基础05

    javaSE基础05:面向对象 一.数组 数组的内存管理 : 一块连续的空间来存储元素. Int [ ] arr = new int[ ]; 创建一个int类型的数组,arr只是一个变量,只是数组的一 ...

  6. javaSE基础04

    javaSE基础04 一.三木运算符 <表达式1> ? <表达式2> : <表达式3> "?"运算符的含义是: 先求表达式1的值, 如果为真, ...

  7. javaSE基础03

    javaSE基础03 生活中常见的进制:十进制(0-9).星期(七进制(0-6)).时间(十二进制(0-11)).二十四进制(0-23) 进制之间的转换: 十进制转为二进制: 将十进制除以2,直到商为 ...

  8. javaSE基础02

    javaSE基础02 一.javac命令和java命令做什么事情? javac:负责编译,当执行javac时,会启动java的编译程序,对指定扩展名的.java文件进行编译,生成了jvm可以识别的字节 ...

  9. JavaSE基础01

    JavaSE基础篇01 ------从今天开始,我就学习正式java了,O(∩_∩)O哈哈~,请大家多指教哦 一.Windows常见的dos命令 操作dos命令: win7 --->开始 --- ...

随机推荐

  1. Oracle中事物处理--事物隔离级别

    n  事物隔离级别 概念:隔离级别定义了事物与事物之间的隔离程度. ANSI/ISO SQL92标准定义了一些数据库操作的隔离级别(这是国际标准化组织定义的一个标准而已,不同的数据库在实现时有所不同) ...

  2. PHP如何输出合并单元格的表

    https://mp.weixin.qq.com/s/ChPIKIv9tqmuqGyfc9Zi7Q 合并单元格的表,很多地方可以见到,比如购物车,订单合并等,今天给大家讲解一下,如何操作,虽然我用的l ...

  3. 开发ArcEngine时的一个提示,无效位图,无法在类“***”中找到资源“***.bmp”

    解决办法:文件属性-生成操作 修改为 :嵌入式资源.

  4. String和Object转换

    http://www.cnblogs.com/mingzi/archive/2009/01/03/1367474.html

  5. time,datetime模块

    time模块 时间戳 返回1970年1月1日 00:00:00开始按秒计算时间偏移量 time_stamp = time.time() print(time_stamp,type(time_stamp ...

  6. icheck的使用

    一.什么是icheck 就是用来美化单选框.复选框的. 二.如何使用 1.下载 到 github 下载.https://github.com/fronteed/icheck 下载完毕.解压.目录结构如 ...

  7. @topcoder - TCO19 Regional Wildcard Wildcard Round - D1L2@ Diophantine

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 令 p[] 为质数序列:p[0] = 2, p[1] = 3, ...

  8. tensorflow学习笔记(三十四):Saver(保存与加载模型)

    Savertensorflow 中的 Saver 对象是用于 参数保存和恢复的.如何使用呢? 这里介绍了一些基本的用法. 官网中给出了这么一个例子: v1 = tf.Variable(..., nam ...

  9. jmeter日期处理beanshell(1)

    import java.time.LocalDate; //昨天: String sdate1 = LocalDate.now().minusDays(1).toString(); vars.put( ...

  10. 在ThinkPHP中,if标签和比较标签对于变量的比较。

    在TP模板语言中.if和eq都可以用于变量的比较. <比较标签 name="变量" value="值">内容</比较标签> 比如: &l ...