——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
线程:线程是进程的一个实体,是CPU调度和分派的基本单位,能够独立执行,有时被称为轻量级进程,是进程中执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
进程与线程的关系:
线程是进程中的一个执行单元,也可说进程有线程组成,一个进程至少一个线程。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
多线程:线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
多线程的意义:因为是并发执行线程,能够有效地提高执行效率,以及充分利用系统资源。
线程的创建:
方式一:继承Thread类
通继承Tread类并重写run方法,并把要在该线程中执行的代码放在run方法中编写,
Thread类的部分方法:
getName():返回该线程的名称。
isAlive():测试线程是否处于活动状态。
run():存放执行代码
sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
stop():用来终止线程将释放它已经锁定的所有监视器
yield():暂停当前正在执行的线程对象,并执行其他线程。
join():等待该线程终止
/**
* 多线程
*/
public static void main(String[] args) {
//新建一个线程
ThreadTest thread = new ThreadTest();
//运行run方法
thread.run();
//主程序的运行代码
for(int i= 0;i<5;i++)
System.out.println(Thread.currentThread().getName()+"------"+i);
}
}
//继承Thread并重写run方法
class ThreadTest extends Thread
{
//重写run方法
@Override
public void run() {
for(int i= 0;i<5;i++)
System.out.println(Thread.currentThread().getName()+"------"+i);
}
}
运行结果:
总运行的结果看,代码的执行过程都是有主线程main在执行,并没有产生新的线程。执行run 方法是不能产生新的线程的。
/**
* 多线程
*/
public static void main(String[] args) {
//新建一个线程
ThreadTest thread = new ThreadTest();
//用start启动线程
thread.start();
//主程序的运行代码
for(int i= 0;i<5;i++)
System.out.println(Thread.currentThread().getName()+"------"+i);
}
}
//继承Thread并重写run方法
class ThreadTest extends Thread
{
//重写run方法
@Override
public void run() {
for(int i= 0;i<5;i++)
System.out.println(Thread.currentThread().getName()+"------"+i);
}
}
运行结果:
从运行的结果看,看到了两个线程名,两线程是同时执行的,看到的结果是两个线程的结果交替打印,说明线程必须有start方法启动,start方法会去调用run方法。
方式二:实现Runnable接口
通过实现Runnable接口,并实现run方法,把Runnable子类对象作为参数传到新建一个Thread的线程。
/**
* 多线程
*/
public static void main(String[] args) {
//新建一个线程
RunnableTest run = new RunnableTest();
Thread thread = new Thread(run);
//用start启动线程
thread.start();
//主程序的运行代码
for(int i= 0;i<5;i++)
System.out.println(Thread.currentThread().getName()+"------"+i);
}
}
//继承Thread并重写run方法
class RunnableTest implements Runnable
{
//重写run方法
@Override
public void run() {
for(int i= 0;i<5;i++)
System.out.println(Thread.currentThread().getName()+"------"+i);
}
}
运行结果:
可以看到上下两个线程的执行代码是一样的,所以说他们的代码效果是一样的,用两种方法都可以创建新的线程。那么为什么要有两种方法呢?用哪一种好呢?
我们知道java是单继承的,如果我们用方法一继承Thread方法,那该类就不能继承其他类,这样不利于程序的扩展性,而java中可以实现多接口,所以java中增加了一个实现接口的方法避免了java单继承带来的影响。一般建议采用第二种方法,能够继承其他类的同时,也可以实现多线程。
线程的生命周期:
创建:线程对象的生成
临时状态:线程对象调用start()方法,线程对象可以运行,具有了执行权,但不一定执行线程,因为有可能其它线程正在执行,占用了CPU的资源,这时候的线程被阻塞。
运行状态:线程从就绪状态获得了CPU资源,线程执行,。
冻结:执行的线程调用了sleep()或wait()方法,线程睡眠或等待,睡眠结束或者用notify()唤醒等待,线程可以回到临时状态
消亡:线程运行结束
结构:
练习:
/**
* 买票程序:
* 同时建立多个窗口进行买票,窗口实现Rnunable,
* 可以同时运行多个窗口进行买票
*/
public class Test12 {
public static void main(String[] args) {
//建立Ticket对象
Ticket ticket = new Ticket();
//建立线程
Thread thread1 =new Thread(ticket);
Thread thread2 =new Thread(ticket);
Thread thread3 =new Thread(ticket);
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
//买票窗口
class Ticket implements Runnable
{
//票数
private int ticket = 100;
//重写run方法
@Override
public void run() {
while(ticket>0)
{
//打印买票的线程,以及票的剩余量
try {
//买票消耗的时间
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+--ticket);
}
}
}
运行结果:
从运行结果看到,每一个线程中卖出票后,飘的剩余量,但是从结果中看到了票数出现-1、-2。票数怎么可以是负数呢?明显说明了程序运行与我们想要的结果不同,那么程序出错在哪?
当多个线程在操作一个对象的数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
线程安全
如何解决数据共享时出现的数据错误,出现错误是因为一个线程访问一个数据的动作还没有做完时,另一个线程获得了执行的资源,再次对数据进行了操作,当前一个线程回来再次执行还没有执行完的代码时,因为已经有另一线程对数据可能进行了修改,所以当线程要把剩余代码执行完时,有可能会出现前后两次的数据不对,造成数据出错。
为了避免数据的共享时出现错误,那就要求当一个线程执行一次数据的操作是时,其他的线程不能进入对数据进行操作,直到该线程执行完这一次操作代码,这样保证了数据共享的安全,这样的设定叫做同步:
如何实现同步?
用关键字synchronized来实现:
1、同步代码块
用法:
synchronized(对象)
{需要被同步的代码}
同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁:
格式:
2、同步函数:
在函数上加上synchronized修饰符即可,同步函数的锁是this。
加上了同步后的程序:
/**
* 买票程序:
* 同时建立多个窗口进行买票,窗口实现Rnunable,
* 可以同时运行多个窗口进行买票
*/
public class Test12 {
public static void main(String[] args) {
//建立Ticket对象
Ticket ticket = new Ticket();
//建立线程
Thread thread1 =new Thread(ticket);
Thread thread2 =new Thread(ticket);
Thread thread3 =new Thread(ticket);
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
//买票窗口
class Ticket implements Runnable
{
//票数
private int ticket = 100;
//重写run方法
private Object obj=new Object();
@Override
public void run() {
{
while(ticket>0)
{
try {
//买票消耗的时间
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印买票的线程,以及票的剩余量
synchronized(obj)
{
//这次判断ticketshi是否大于0
if(ticket>0)
{
System.out.println(Thread.currentThread().getName()+" "+--ticket);
}
}
}
}
}
}
运行结果:
同步死锁
同步解决了多线程的数据共享的时可能造成的错误,但是也带来了一个问题,多线程间可能会出现同步死锁:当两个线程同时执行时,而在代码在有两个锁,当每一个线程各占用了一个锁时,而这时候两个线程都要得到对方的锁在可以继续执行下去时,这两线程就会在阻塞状态,等待对方放出锁,而这时候的两个线程都不会放出当前的锁,这样就造成程序无法执行下去。
死锁代码:
/**
* 同步死锁
*
*/
public class Test12 {
public static void main(String[] args) throws InterruptedException {
//启动两线程
RunTest run = new RunTest();
Thread thread1 = new Thread(run);
Thread thread2 = new Thread(run);
thread1.start();
Thread.sleep(10);
run.flag = false;
thread2.start();
}
}
//线程的两个锁
class Lock
{
public static Object lock1 = new Object();
public static Object lock2 = new Object();
}
//线程
class RunTest implements Runnable
{
public boolean flag = true;
@Override
public void run() {
if(flag)
{
while(true)
{
synchronized(Lock.lock1)
{
synchronized(Lock.lock2)
{
System.out.println("true");
}
}
}
}else
{
while(true)
{
synchronized(Lock.lock2)
{
synchronized(Lock.lock1)
{
System.out.println("false");
}
}
}
}
}
}