"${a[*]}" expands to one string for all entries together and "${a[@]}" expands to one string per entry.
Assume we had a program printParameters, which prints for each parameter ($1, $2, and so on) the string my ... parameter is ....
>_ a=('one' 'two')
>_ printParameters "${a[*]}"
my 1. parameter is one two
>_ printParameters "${a[@]}"
my 1. parameter is one
my 2. parameter is two
If you would expand the array manually, you would write
${a[*]} as "one two" and
${a[@]} as "one" "two".
There also differences regarding IFS and so on (see other answers). Usually @ is the better option, but * is way faster – use the latter one in cases where you have to deal with large arrays and don't need separate arguments.
By the way: The script printParameters can be written as
#! /bin/bash
argIndex=0
for argValue in "$@"; do
    echo "my $((++i)). argument is $argValue"
done
It's very useful for learning more about expansion by try and error.